Given:
// FileReader interface for reading from a file
type FileReader interface {
ReadFile(filename string) ([]byte, error)
}
type FileRead struct {}
// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
return ioutil.ReadFile(fn)
}
type Dev struct {
*FileRead
}
func NewDev() *Dev {
frd := FileRead{}
return &Dev{frd}
}
// Function that does some job
func (dev Dev) DoSomeStuff() {
//...
dev.ReadFile("file")
//...
}
func main () {
doer := NewDev()
doer.DoSomeStuff()
}
During unit testing, the ReadFile operation should be mocked. How can one best achieve this in go test?
Dev struct could instead use FileReader interface, but then struct embedding is no longer used and the syntax in DoSomeStuff becomes less obvious.
If you are using a DI framework such as Dargo you can inject something that implements FileReader into dev. In your main-line code you would bind the normal FileReader, but in your test you can use a mock FileReader. The rest of your code should not know the difference. It would look something like this:
import (
"github.com/jwells131313/dargo/ioc"
"io/ioutil"
"testing"
)
type FileReader interface {
ReadFile(filename string) ([]byte, error)
}
type FileRead struct{}
// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
return ioutil.ReadFile(fn)
}
type Dev struct {
MyReader FileReader `inject:"FileReader"`
}
/* Not here anymore, but you can implement DargoInitializer
if you need more initialization of Dev
func NewDev() *Dev {
frd := FileRead{}
return &Dev{frd}
}
*/
// Function that does some job
func (dev Dev) DoSomeStuff() {
//...
dev.MyReader.ReadFile("file")
//...
}
var locator ioc.ServiceLocator
func initialize() error {
l, err := ioc.CreateAndBind("Example", func(binder ioc.Binder) error {
binder.Bind("Dev", &Dev{})
binder.Bind("FileReader", &FileRead{})
return nil
})
locator = l
return err
}
func main() {
initialize()
raw, _ := locator.GetDService("Dev")
doer := raw.(*Dev)
doer.DoSomeStuff()
}
// Here is test code
type TestFileRead struct{}
// ReadFile is a mock that just returns a zero-length byte array
func (tfr TestFileRead) ReadFile(fn string) ([]byte, error) {
return []byte{}, nil
}
func TestMe(t *testing.T) {
initialize()
ioc.BindIntoLocator(locator, func(binder ioc.Binder) error {
binder.Bind("FileReader", &TestFileRead{}).Ranked(1)
return nil
})
// Test Me!
}
In the above example the "normal" file reader is injected in the normal code, but in the test there is a TestFileReader with a higher rank. So when you went to get Dev in the test it would be injected with the test one, not the one from the main-line code.
Hope this helps.
Related
I am using juniper's netconf package ("github.com/Juniper/go-netconf/netconf") to establish a netconf session in my code.
I wanted to know how can I mock a netconf session in my unit tests.
My methods are:
func TestMyFunction(t *testing.T) {
getSSHConnection = mockGetSSHConnection
got := MyFunction()
want := 123
if !reflect.DeepEqual(got, want) {
t.Errorf("Error expectation not met, want %v, got %v", want, got)
}
}
func mockGetSSHConnection() (*netconf.Session, error) {
var sess netconf.Session
sess.SessionID = 123
return &sess, nil
}
The problem arises when MyFunction() has a line that defers sess.Close() and it's throwing error due to nil pointer dereference
func MyFunction() int {
sess, err := getSSHConnection() // returns (*netconf.Session, error)
if err == nil && sess != nil {
defer sess.Close() -> Problem happens here
// Calls RPC here and rest of the code here
}
return 0
}
So, what changes can I make on mockGetSSHConnection() method so that sess.Close() won't throw error?
The nil pointer error originates within the Close function when Close is called on the underlying Transport. Fortunately Transport is an interface type that you can easily mock and use in an actual instance of the netconf.Session. For example like so:
type MockTransport struct{}
func (t *MockTransport) Send([]byte) error {
return nil
}
func (t *MockTransport) Receive() ([]byte, error) {
return []byte{}, nil
}
func (t *MockTransport) Close() error {
return nil
}
func (t *MockTransport) ReceiveHello() (*netconf.HelloMessage, error) {
return &netconf.HelloMessage{SessionID: 123}, nil
}
func (t *MockTransport) SendHello(*netconf.HelloMessage) error {
return nil
}
func (t *MockTransport) SetVersion(version string) {
}
func mockGetSSHConnection() (*netconf.Session, error) {
t := MockTransport{}
sess := netconf.NewSession(&t)
return sess, nil
}
Note that the function you want to test currently return 0 and not the SessionID of the session. So you should fix that before the test is successful.
You could use OOP and "github.com/stretchr/testify/mock" package
for example create
type SshClientMock struct {
mock.Mock
}
func (s *SshClientMock) GetSSHConnection() {
return //what do you need
}
in your unit test:
sshClient := SshClientMock
sshClient.On("GetSSHConnection").Return(what do you need)
and then call your method
This is the code I am testing
func listObjects(cli *client.Client, options clientOptions) ([]BlobObjects, error) {
objects, err := cli.ListBlobObjects(...)
}
In my test setup I do this
type MockClient struct {
MockListBlobObjects func() ([]BlobObjects, error)
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
And this is my test case
func TestBlobObjects(t *testing.T) {
tests := map[string]struct {
client *MockClient
...
}{
"Test case 1": {
client: &MockClient{
MockListBlobObjects: ....,
},
....
},
....
for testName, test := range tests {
blobs, err := (test.client, clientOptions{})
// make assertions here
}
The problem is test.client. Compiler is telling me
cannot use test.client (variable of type *MockClient) as *client.Client value in argument to listObjects
My hope was I have a mock client and if I call the function under test, then the mock client passed will call the mocked listObjects. This is how I would do in Python.
What should I do in Golang?
You need to create a common interface, which will be implemented by both types
type Client interface {
ListBlobObjects func() ([]BlobObjects, error)
}
type RealClient struct {
}
func (c *RealClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some response
}
type MockClient struct {
// note: don't put method signature in function body
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
and then
var client client.Client
client = &RealClient{}
// or
client = &MockClient{}
I'm having the following problem when unit testing (working code here]
Assuming we have this:
var (
userDomain UserInterface
Tx TxInterface
)
type UserInterface interface {
Get() (*User, error)
}
type TxInterface interface {
Exec(fn func() error) error
}
type User struct {
ID int64
}
func (u *User) Get() (*User, error) {
return &User{
ID: 1,
}, nil
}
type WithTx struct{}
func (t *WithTx) Exec(fn func() error) error {
/* more logic here */
return fn()
}
Assuming we have the following 2 services:
func GetByID() (*User, error) {
user, err := userDomain.Get()
return user, err
}
func GetByIDWithTx() (*User, error) {
u := &User{}
/** Notice this implementation **/
if err := Tx.Exec(func() error {
user, _ := userDomain.Get()
u = user
return nil
}); err != nil {
log.Print("Tx err", err)
}
return u, nil
}
They are the same but once goes directly to the Get method, the other one runs into a callback fn. So far so go, both works like a charm. Again code is here
I'm trying to mock the userDomain and Tx for unit tests, so my mocks look like this:
var (
getUserFromMock func() (*User, error)
getFromMock func() error
)
type userMock struct{}
func (u *userMock) Get() (*User, error) {
return getUserFromMock()
}
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return getFromMock()
}
When testing GetByID I have no problem whatsoever.
func TestGetByID(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 3}, nil
}
user, err := GetByID()
if user.ID != 3 {
t.Fatalf("error")
}
if err != nil {
t.Fatalf("error")
}
}
However, When I try to testing GetByIDWithTx, I cannot assert the mock values:
func TestGetByIDWithTx(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 4}, nil
}
/**** Mocking callback response ****/
getFromMock = func() error {
return nil
}
user, err := GetByIDWithTx()
if user.ID != 4 {
t.Fatalf("error") /** It fails here **/
}
if err != nil {
t.Fatalf("error")
}
}
I assume, at the moment I'm trying to assert the callback is still running in another routine but I'm just speculating, I don't have any idea what is going on here. I rather not use any library for a spy, I just want to understand how to solve this. Thoughts?
Go PlayGround HERE
You are never calling the fn given to the txmock. Change txMock.Exec to:
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return fn()
// return getFromMock()
}
and remove getFromMock() function completely. It will then call getUserFromMock function to get the mocked user.
Hi I would like to test or Mock a certain function and return a Mock response for this. To demonstrate below is my code
Sample.go
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
)
var connectDB = Connect
func Sample() {
config := NewConfig()
response := connectDB(config)
fmt.Println(response)
log.Info(response)
}
func Connect(config *Config) string {
return "Inside the connect"
}
And my test is like this
Sample_test.go
package main
import (
"testing"
)
func TestSample(t *testing.T) {
oldConnect := connectDB
connectDB := func(config *Config) string {
return "Mock response"
}
defer func() { connectDB = oldConnect }()
Sample()
}
So when running go test I was expecting to receive and output of Mock response but I'm still getting Inside the connect. Is there something I'm missing here?
#jrefior is correct, but I'd suggest to use interface for mocking. Of course, it's up to you, bet for me it is more clear, but more complicated code :)
// lack some fields :)
type Config struct {
}
// Use interface to call Connect method
type IConnection interface {
Connect(config *Config) string
}
// Real connection to DB
type Connection struct {
}
func (c Connection) Connect(config *Config) string {
return "Inside the connect"
}
// Mock connection
type MockConnection struct {
}
func (c MockConnection) Connect(config *Config) string {
return "Mock connection"
}
// Accepts interface to connect real or mock DB
func Sample(con IConnection) {
log.Println(con.Connect(nil))
}
func main() {
realConnection := Connection{}
Sample(realConnection)
mockConnection := MockConnection{}
Sample(mockConnection)
}
The use of a colon here creates a new function-scoped variable with the same name:
connectDB := func(config *Config) string {
return "Mock response"
}
Remove the colon to assign to the package variable.
I am still grasping go-interfaces and I can mock the WaitUntilTableExists func. But unable to mock PutItemRequest.
Here's my main.go snippet
func MyPutItem(d mydata, client dynamodbiface.DynamoDBAPI) error {
input := &dynamodb.PutItemInput{
....
}
req := client.PutItemRequest(input)
result, err := req.Send()
log.Println(result)
return err
}
main_test.go snippet
type mockDynamoDBClient struct {
dynamodbiface.DynamoDBAPI
}
func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemRequest {
// Most probably this is where I need your help
}
func TestStoreInDynamoDB(t *testing.T) {
var mockClient = new(mockDynamoDBClient)
d := mydata{}
result := DynampDBPutItem(d, mockClient)
t.Log(result)
}
Taking your example, you could do your assertions directly in the mock
type mockDynamoDBClient struct {
t *testing.T
expected *dynamodb.PutItemInput
response *dynamodb.PutItemOutput
dynamodbiface.DynamoDBAPI
}
func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemOutput {
// some kind of equality check
if !reflect.DeepEqual(m.expected, input) {
t.Errorf(...// some error message)
}
return m.response
}
The main problems with this example are:
t *testing.T, expected *dynamodb.PutItemInput and response response *dynamodb.PutItemOutput all need to be inside the struct which feels messy.
Instead you could use an anonymous function to do this:
type mockDynamoDBClient struct {
f func(input *dynmaodb.PutItemInput) *dynamodb.PutItemOutput
dynamodbiface.DynamoDBAPI
}
func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemOutput {
return m.f(input)
}
Now in the test code you can make slightly better use of the mock struct:
m := &mockDynamoDBClient{
f: func(input *dynamodb.PutItemInput) *dynamodb.PutItemOutput {
// assertions on input
// return mock responses
}
}
EDIT based on comment:
You should also consider making your MyPutItem function dependent on the smallest interface possible. If you only need access to the PutItemRequest method then you can create your own interface for that method and use that in MyPutItem
type MyDynamoPutter interface {
func (c *DynamoDB) PutItemRequest(input *PutItemInput) PutItemRequest
}
Then in MyPutItem you can use your own interface:
func MyPutItem(d mydata, client MyDynamoPutter) error {
input := &dynamodb.PutItemInput{
....
}
req := client.PutItemRequest(input)
result, err := req.Send()
log.Println(result)
return err
}
This reduces the surface area that you need to mock!
Faking the SDK like this works:
main_test.go
type fakeDynamoDBClient struct {
dynamodbiface.DynamoDBAPI
}
func (m *fakeDynamoDBClient) GetItemRequest(input *dynamodb.GetItemInput) dynamodb.GetItemRequest {
return dynamodb.GetItemRequest{
Request: &aws.Request{
Data: &dynamodb.GetItemOutput{
Item: map[string]dynamodb.AttributeValue{
"count": dynamodb.AttributeValue{
N: aws.String("10"),
},
},
},
},
}
}
func (m *fakeDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemRequest {
return dynamodb.PutItemRequest{
Request: &aws.Request{
Data: &dynamodb.PutItemOutput{},
},
}
}
func TestUpdateCount(t *testing.T) {
err := UpdateCount(10, &fakeDynamoDBClient{})
if err != nil {
t.Error("Failed to update badge count on dynamodb", err)
}
}
main.go
func UpdateCount(count int, client dynamodbiface.DynamoDBAPI) error {
...
}