In this blog, I’ll share how write unit tests mocking methods to avoid call them, this is a basic guide for beginners/intermediates in testing and mocks. This could be helpful in our daily software development process, and starting to use good practices to test our code.
Your unit tests should examine the behavior of the method/function, in some cases you have external dependencies like other API’s. It is not recommended to make requests to those API’s for the following reasons:
- External API might be down.
- Your requests may have a cost.
- Your requests may belong a financial technology(fintech)
The solution for these issues is mock external dependencies. In this case we are going to see and use interfaces to mock third parties.
Golang interfaces are amazing because they are implicitly. It means in Go interfaces are implicitly implemented just using structs. You don’t have to use implements
or something like that.
Let’s create an example how it works.
First I’ve created the following scenario:
We have a service that uses some important credentials, so I need read a few params from toml file, lets code.
First I’m going to create function and struct to read parameters from toml.
type Conf struct {
Pass string
User string
Token string
}type ReadConf interface {
Config() *Conf
}func (c Conf) Config() *Conf {
var conf Conf
if _, err := toml.DecodeFile("your/path", &conf); err != nil {
log.Fatalln(err)
}
return &conf
}
In code you can see how interface is implemented, Go Programming Language book explains that you add a new parameter at the beginning of the func to implement interface, from this moment this func changes to a method with this struct func (struct c) name() {}
Now to read the interface from other domain(package) let’s use the interface and initialize with init
func to avoid the famous nil pointer dereference
. I like this way because of concept of DDD with ports and adapters
architecture, the package that will use this logic won’t know anything about this class, it only knows that it need some information that will come from config. Then I’ll write about it.
func init() {
Rc = &Conf{}
}var (
Rc ReadConf
)func ReadConfig() *Conf {
return Rc.Config()
}
Now let’s create the func to read this conf. It could be in another package or directory. Now to call this config method you can write the following. This is the easy part because we only want to get the information from config.
func UseCredentials() (string, *config.Conf) {
conf := config.ReadConfig()
return "your information is: ", conf
}
At this moment we gonna start with unit test, we are going to test UseCredentials()
create a new file next to the class(tests should be always in the same package). To assert the result I’m going to use testify
that is a good library to write asserts in unit tests in Go.
func Test_useCredentials(t *testing.T) { str, conf := UseCredentials() assert.NotNil(t, str)
assert.NotNil(t, conf)
assert.EqualValues(t, "your information is: ", str)
assert.EqualValues(t, "user", conf.User)
assert.ObjectsAreEqual(config.Conf{}, conf)
assert.IsType(t, config.Conf{}, *conf, conf, " isn't the type expected")
}
Until now every thing seems normal, now the point of this blog is mock’s so let’s start with it. So first create a new package/directory I named utils to use it for more things later, inside utils y create another package mocks then we are going to implement Config() method
that we created at the beginning and create a var with a new func that fetch the mock’s Config
func.
type MockClient struct {
}
var (
// GetConfig fetches the mock client's `Config` func
GetConfig func() *config.Conf
)
//Config is the mock client's `Config` func
func (m MockClient) Config() *config.Conf {
return GetConfig()
}
The file mock.go at least should be like this.
It’s almost done! now to finish let’s edit unit test. First we’ve to add init()
function to tell Go that we want that config don’t use the original struct, instead we are going to tell Go that we want that test run with struct in mock class MockClient
func init() {
config.Rc = &mocks.MockClient{}
}
This way interface is going to implement method in mock class returning GetConfig()
that is a function we use in the same test with our own data(mocking data) as following.
mocks.GetConfig = func() *config.Conf {
return &config.Conf{
Pass: "pass",
User: "alonyb",
Token: "0987654321abcde",
}
}
Finally our test class should be like this.
This way we never used Config()
just the interface to redirect to a mock function with our own data.