I have recently decided to dip my toes into the concurrency pool and I’m currently losing the will to live. I know this is going to be simple for you well seasoned Gophers, but it’s breaking me!
Let me give you some context. I have an interface (called Vendor
) which has a single method Search
. I am creating a struct which holds a slice of these Vendors. Like so:
type VendorAggregator struct {
vendors []Vendor
}
VendorAggregator
has a single method called Get
which will call a go routine on each of the elements in the vendors
slice, aggregate the responses and send them back to the consumer. Simples??
Well, when I write a test to prove the function; I keep getting duplicate responses from only one of the elements in the slice.
type VendorMockA struct{}
func (va *VendorMockA) Search(p *models.Product) ([]models.Product, error) {
return []models.Product{{"test", "vendora", 100, "g"}}, nil
}
type VendorMockB struct{}
func (vb *VendorMockB) Search(p *models.Product) ([]models.Product, error) {
return []models.Product{{"test", "vendorb", 100, "g"}}, nil
}
func TestVendorAggregator_Get(t *testing.T) {
// Given: a VendorAggregator with 2 mock services
subject := &VendorAggregator{[]Vendor{
&VendorMockA{},
&VendorMockB{},
},
}
// When: I request a single product
actual, _ := subject.Get(&models.Product{"test", "test", 100, "g"})
// Then: I expect a response from each vendor
lenExpected := len(subject.vendors)
if len(actual) != lenExpected {
t.Errorf("Get returned %v results, but expected %v",
len(actual), lenExpected)
}
}
Admittedly, the test is not checking content. I do that through stepping through the code. Below is the value of actual
.
For compleness, here is the actual Get
method:
func (va *VendorAggregator) Get(p *models.Product) ([]models.Product, error) {
ch := make(chan *VendorResponse)
responses := []models.Product{}
for _, vendor := range va.vendors {
// Using the go keyword, we define an anonymous function that takes a vendor type and product.
// The function then makes a Search call on the vendor taking a requested product. We use the
// returned resp and err to create an instance of our VendorResponse type and send it to the channel.
go func(v Vendor, p *models.Product) {
resp, err := vendor.Search(p)
ch <- &VendorResponse{resp, err}
}(vendor, p)
}
for {
select {
case r := <-ch:
if r.err != nil {
fmt.Println("with an error", r.err)
}
responses = append(responses, r.inventory...) // the three dots are important for concatinating 2 slices
if len(responses) == len(va.vendors) {
return responses, nil
}
case <-time.After(50 * time.Millisecond):
log.Printf(".")
}
}
return responses, nil
}
Any help would be gratefully received.