TestDouble framework for Go.
To learn about Go. Sane folks should probably use gomock, testify etc...
This framework creates a TestDouble implementation of an interface which can be substituted for the real thing during tests. Interface methods can then individually be Stubbed, Mocked, Spied upon or Faked as required.
See the canonical sources...
http://xunitpatterns.com/Test%20Double.html
https://martinfowler.com/articles/mocksArentStubs.html
Create a struct that includes the interface and pointer to godouble.TestDouble
type APIDouble struct {
API
*godouble.TestDouble
}
func NewAPIDouble(t godouble.T,configurators) *APIDouble {
return &APIDouble{TestDouble: godouble.NewDouble(t,(*API)(nil))}
}
Implement Interfaces methods calling godouble.Invoke and converting the return types
func (d *APIDouble) SomeQuery( input string) (r Results,e error) {
returns := godouble.Invoke(d,"SomeQuery",input)
r, _ = returns[0].(Results)
e, _ = returns[1].(error)
return
}
Create a generator command that uses doublegen.NewGenerator over the interface
// +build doublegen
package main
func main() {
if f, e := os.Create("example_double_test.go"); e == nil {
defer f.Close()
doublegen.NewGenerator((*examples.API)(nil)).GenerateDouble(f)
} else {
log.Fatal(e)
}
}
Add go:generate tag in a test file
//go:generate go run -tags doublegen doublegen/example_gen.go
func Test_Mock(t *testing.T) {
d := NewAPIDouble(t)
//...
}
Run go generate
$ go generate
2020/01/30 22:30:55 Generated Double for examples.API
A stub provides specific return values for a matching call to the method. Most useful where the return values are the primary means by which correct operation of the system under test can be verified.
package examples
import (
. "github.com/lwoggardner/godouble" //Note the dot import which assists with readability
"testing"
)
func Test_Stub(t *testing.T) {
d := NewAPIDouble(t)
//Stub a method that receives specific arguments, to return specific values
d.Stub("SomeQuery").Matching(Arguments(Eql("test"))).Returning(Values(Results{"result"}, nil))
// Exercise the system under test substituting d for the real API client
// ...
// Verify assertions to confirm the system under test behaves as expected with the given return values
// ...
}
A Mock is a Stub with an up-front expectation for how many times it will be called. Most useful when the return values of the method do not completely ensure correct functioning of the system under test,
func Test_Mock(t *testing.T) {
d := NewAPIDouble(t)
// Verify the mock expectations are met at completion
defer d.Verify()
//Stub a method that receives specific arguments, returns specific values and explicitly expects to be called once
d.Mock("SomeQuery").Matching(Arguments(Eql("test"))).Returning(Values(Results{"result"}, nil)).Expect(Exactly(3))
d.Mock("OtherMethod").Expect(Never())
//Exercise...
}
A Spy is a record of all calls made to a method which can be verified after exercising the system under test. Used similarly to Mock, but where you prefer to explicitly assert received arguments and call counts in the Verify phase of the test.
func Test_Spy(t *testing.T) {
//Setup
d := NewAPIDouble(t)
spy := d.Spy("SomeQuery").Returning(Values(Results{"nothing"}, nil))
//Exercise...
//Verify
spy.Expect(Twice()) //All calls
spy.Matching(Arguments(Eql("test"))).Expect(Once()) //The subset of calls with matching args
}
A Fake is a Spy that provides an actual implementation of the method instead of return values. Use with caution.
func Test_Fake(t *testing.T) {
//Setup
d := NewAPIDouble(t)
impl := func( i int, options...string) *Results {
return &Results{Output: fmt.Sprintf("%s %d",strings.Join(options," "),i)}
}
spy := d.Fake("QueryWithOptions",impl)
//Exercise...
//Verify
spy.Expect(Twice())
spy.Matching(Arguments(Eql(10))).Expect(Once())
}
Used in Stubs and Mocks to Setup whether the arguments in a particular call will match the stub.
Used in Spies and Fakes to select a subset of received calls from the set of all recorded calls or to take subsets of subsets
Simple implementations are provided. eg for deep equality
Used in Stubs, Mocks and Spies to generate values from potentially successive calls to the method.
Simple implementations are provided. eg for fixed values, channel of values, randomly delayed values
Used in Mocks to Setup expectation on the number of times the matching method will be called
Used in Spies and Fakes to explicitly verify the number of times the method was called.
eg Once(), Twice(), Never(), Exactly(n) AtLeast(n), AtMost(n), Between(n,m)
Mocks can be setup to expect being called After another mock call (to any method of any double), or a sequence of mocks can be setup to verify that they execute InOrder
Spies and Fakes can select a subset of calls that were made After another subset of calls (to any method of any double).