Golang “Mocking” a Function for Unit Testing
Continuing our basic Golang unit testing example, we are going to continue on another step of simple function mocking for unit testing in Go. In here We’re only going cover this two “kind” of function:
- Function in your own package
- Function imported (from library or stuff) outside of your project
Therefore, we’re not going to “mock” method which belongs to a struct right now. You might ask, why do I need to mock a function? Can we just call the function as is? The simple answer is, if your function contains API / DB call (external call), you should mock it in your test environment.
So Let’s Get Started
Let say you have a function like this
type Person struct { ID int `db:"id" json:"id"` Name string `db:"name" json:"name"` BornDate string `db:"born_date" json:"born_date"`}type Address struct { ID int `db:"id" json:"id"` UserID int `db:"user_id" json:"user_id"` Street string `db:"street" json:"street"` PostalCode string `db:"postal_code" json:"postal_code"`}type UserProfile struct { User Person `json:"person"` Address Address `json:"address"`}func GetPersonByID(id int) (*Person, error) { var user Person err := db.Get(&user, `SELECT id, name, born_date FROM users WHERE id = ?`, id) if err != nil { return nil, err } return &user, err}func GetPersonAddrByUserID(userID int) (*Address, error) { var addr Address err := db.Get(&addr, `SELECT id, user_id, street, postal_code FROM address WHERE user_id = ?`, userID) if err != nil { return nil, err } return &addr, err}func GetUserProfile(userID int) (UserProfile, error) { var userProfile UserProfile p, err := GetPersonByID(userID) if err != nil { return userProfile, err } a, err := GetPersonAddrByUserID(userID) if err != nil { return userProfile, err } userProfile.User = *p userProfile.Address = *a return userProfile, nil}func JSONMarshalUserProfile(up UserProfile) ([]byte, error) { return json.Marshal(up)}
Then you want to test these two functions (since they depends on function that connect to DB / external lib) and you want them not to do that in test environment (if you want to control what the function returning):
- GetUserProfile
- JSONMarshalUserProfile
We’re not going to cover the mocking DB function for this writing, therefore GetUserByID and GetPersonAddress database mocking won’t be covered right now.
Do you know you can put your function as a variable? Therefore we can create the function like these (simplified)
var GetPersonByID = func(id int) (*Person, error) {
var user Person
.... // function content
}var GetPersonAddrByUserID = func(userID int) (*Address, error) {
var addr Address
... // function content
}
After modifying the function, we can mock / override your function implementation like this.
GetPersonByID = func(id int) (*Person, error) {
// PUT DIFFERENT IMPLEMENTATION HERE
}
So When you’re writing the unit test, you can put it all like these.
Inside the mockFunc, we will mock both functions (GetPersonByID and GetPersonAddrByUserID) to just return a constant value depends on our test case. Take a note that we need to save the original function so we can revert the function (variable) back to its original implementation after the unit test is done (line 96,97,115,116).
Function From External
So what about function from a library / another project packages? For example, let say we have this function
func JSONMarshalUserProfile(up UserProfile) ([]byte, error) { return json.Marshal(up)}
We’re going to do the same approach but a little bit different. We can save the json.Marshal to a variable, ie like this:
var marshalJSON = json.Marshal
func JSONMarshalUserProfile(up UserProfile) ([]byte, error) { return marshalJSON(up)}
If you want to test JSONMarshalUserProfile, you can easily mock the marshalJSON function like the previous method since it’s just a variable which you can override.
marshalJSON = func(v interface{}) ([]byte, error) { // Put your mocking / testing implementation here
}
As you can see, it also works for external package, i know some of you might think that this is not clean for some reason, like too many global variables of function (if you’re using the methods for a lot of external function). We’re going to cover that in the future discussion about leveraging an interface for cleaner mocking.
Hope this useful for you, see ya :)