I'm currently looking into creating some unit tests for my service in Go, as well as other functions that build up on top of that functionality, and I'm wondering what is the best way to unit test that in Go? My code looks like:
type BBPeripheral struct {
client *http.Client
endpoint string
}
type BBQuery struct {
Name string `json:"name"`
}
type BBResponse struct {
Brand string `json:"brand"`
Model string `json:"model"`
...
}
type Peripheral struct {
Brand string
Model string
...
}
type Service interface {
Get(name string) (*Peripheral, error)
}
func NewBBPeripheral(config *peripheralConfig) (*BBPeripheral, error) {
transport, err := setTransport(config)
if err != nil {
return nil, err
}
BB := &BBPeripheral{
client: &http.Client{Transport: transport},
endpoint: config.Endpoint[0],
}
return BB, nil
}
func (this *BBPeripheral) Get(name string) (*Peripheral, error) {
data, err := json.Marshal(BBQuery{Name: name})
if err != nil {
return nil, fmt.Errorf("BBPeripheral.Get Marshal: %s", err)
}
resp, err := this.client.Post(this.endpoint, "application/json", bytes.NewBuffer(data))
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(resp.StatusCode)
}
var BBResponse BBResponse
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &BBResponse)
if err != nil {
return nil, err
}
peripheral := &Peripheral{}
peripheral.Model = BBResponse.Model
if peripheral.Model == "" {
peripheral.Model = NA
}
peripheral.Brand = BBResponse.Brand
if peripheral.Brand == "" {
peripheral.Brand = NA
}
return peripheral, nil
}
Is the most efficient way of testing this code and the code that uses these functions to spin up a separate goroutine to act like the server, use http.httptest package, or something else? that's the first time that i try to write a test then i don't realy know how.
It really completely depends. Go provides pretty much all the tools you need to test your application at every single level.
Unit Tests
Design is important because there aren't many tricks to dynamically provide mock/stub objects. You can override variables for tests, but it unlocks all sorts of problems with cleanup. I would focus on IO free unit tests to check that your specific logic works.
For example, you could test BBPeripheral.Get
method by making client
an interface, requiring it during instantiation, and providing a stub one for the test.
func Test_BBPeripheral_Get_Success(*testing.T) {
bb := BBPeripheral{client: &StubSuccessClient, ...}
p, err := bb.Get(...)
if err != nil {
t.Fail()
}
}
Then you could create a stub error client that exercises error handling in the Get
method:
func Test_BBPeripheral_Get_Success(*testing.T) {
bb := BBPeripheral{client: &StubErrClient, ...}
_, err := bb.Get(...)
if err == nil {
t.Fail()
}
}
Component/Integration Tests
These tests can help exercise that each individual unit in your package can work together in unison. Since your code talks over http, Go provides the httptest
package that could be used.
To do this the test could create an httptest server with a handler registered to provide the response that this.endpoint
expects. You could then exercise your code using its public interface by requesting a NewBBPeripheral
, passing in this.endpoint
corresponding to the Server.URL
property.
This allows you to simulate your code talking to a real server.
Go Routine Tests
Go makes it so easy to write concurrent code, and makes it just as easy to test it. Testing the top level code that spawns a go routine that exercises NewBBPeripheral
could look very much like the test above. In addition to starting up a test server your test will have to wait your your asynchronous code to complete. If you don't have a service wide way to cancel/shutdown/signal complete then one may be required to test it using go routines.
RaceCondition/Load Testing
Using go's built in bechmark test combined with -race
flag, you can easily exercise your code, and profile it for race conditions, leveraging the tests you wrote above.
One thing to keep in mind, if the implementation of your application is still in flux, writing unit tests may cost a large amount of time. Creating a couple tests, which exercise the public interface of your code, should allow you to easily verify that your application is working, while allowing the implementation to change.