--- title: Functional Options for testing without mocks in Golang author: Rohan Verma type: post date: 2018-07-30T16:55:48+00:00 url: blog/2018/07/30/functional-options-for-testing-without-mocks-in-golang/ categories:
go
type Server struct {
logger *logrus.Logger // optional
store databaste.Store // required
}
type ServerOption func(Server) Server
func WithLogger(logger *logrus.Logger) ServerOption {
return func(s Server) Server {
s.logger = logger
return s
}
}
func NewServer(store database.Store, options ...ServerOption) *Server {
s := Server{store: store}
for _, option := range options {
s = option(s)
}
return &s
}
func main() {
myServer := NewServer(myStore, WithLogger(myLogger))
}
In the above example, we can set the logger without having to depend on config structs and obfuscating the API.
Now that we have potentially solved configuration issues, we can move on to testing. To avoid writing mock functions, we can inject a function that actually performs the request. This way, the default method will be to use the actual implementation but the test can inject a function which simply returns the data we want to check in a way that would be easier for us to test with.
go
// app.go
// WithRequestSender sets the RequestSender for MyStruct.
func WithRequestSender(fn func([]byte, *MyStruct)) Option {
return func(f *MyStruct) {
f.RequestSender = fn
}
}
// app_Test.go
func TestMyStruct_save(t *testing.T) {
var result string
getResult := func(s []byte, p *MyStruct) {
result = string(s)
}
p := New(
WithLogger(log.New(os.Stdout, "TEST: ", log.Ldate|log.Ltime|log.Lshortfile)),
WithQueueSize(1000),
WithRequestSender(getResult),
)
Convey("Given some Content is created with some initial values", t, func() {
s := Content{
Token: 123,
}
Convey("When the struct is inserted into the queue and save is called", func() {
q := *p.GetFromQueue()
q <- s
p.save()
Convey("Then the result created by struct to be sent to endpoint", func() {
Convey("The result should begin with [", func() {
So(result[0], ShouldEqual, '[')
})
Convey("The result should end with ]", func() {
So(result[len(result)-1], ShouldEqual, ']')
})
Convey("The result should contain statement", func() {
So(result, ShouldContainSubstring, string(MyStmt))
})
})
})
})
}
The above way, enables us to check data that might be coming to us in some convoluted way without ever having to write complicated unreadable code or having to modify much of the actual implementation.
[1]: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis