I have been asked a lot about how to test certain cases for certain functions.
The first solution can be to split in more functions and the other can be to change your functions in literals.
In Golang, you can use 2 types of function definitions:
A function declaration binds an identifier, the function name, to a function. So the function name will be an identifier which you can refer to.
func do() {}
A function literals represents an anonymous function. Function literals are closures, they capture the surrounding environment: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.
var do = func() {}
Here is a basic example of how to define a function declaration:
package main
func do() {}
func main() {
go do()
}
Go playground: https://go.dev/play/p/mwxVp3BODjj.
Let’s take the previous example and switch it to literals mode:
package main
import "fmt"
var do = func() { fmt.Println("Doing...") }
func main() {
do()
do = func() { fmt.Println("Not doing!") } // be careful to this do it only in tests
do()
}
Go playground: https://go.dev/play/p/UDsQfjJ4B3F.
Output of the above example:
Doing...
Not doing!
As you can see you can assign a declarative function to a literal function and use it in your tested function and change it in your tests.
This writing is very useful to mock functions in tests, that’s what we’ll see next.
If you want to go further in your tests and test the input parameters of the functions called by the tested function you can do like this.
main.go:
package main
import (
"fmt"
"os"
)
var osExit = os.Exit
func MyFunc() {
fmt.Println("https://a-trick-a-day.github.io")
osExit(1) // I want to test it
}
main_test.go:
package main
import "testing"
func TestMyFunc(t *testing.T) {
// Save current function and restore at the end:
oldOsExit := osExit
resetMock := func() { osExit = oldOsExit }
defer resetMock()
t.Run("success", func(t *testing.T) {
// restore at each test to be sure
resetMock()
var got int
osExit = func(code int) {
got = code
}
MyFunc()
if exp := 1; got != exp {
t.Errorf("Expected exit code: %d, got: %d", exp, got)
}
})
}
https://a-trick-a-day.github.io
PASS
coverage: 100.0% of statements
ok foo 0.002s
This trick can also be used to mock the returns of a function and see how your function handle it.
main.go:
package main
// this function can be in the same package or in another one
var MyFuncFromExternalPackage = func() error {
return nil
}
func MyFunc() {
if err := MyFuncFromExternalPackage(); err != nil {
panic(err)
}
}
main_test.go:
package main
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMyFunc(t *testing.T) {
// Save current function and restore at the end:
oldMyFuncFromExternalPackage := MyFuncFromExternalPackage
resetMock := func() { MyFuncFromExternalPackage = oldMyFuncFromExternalPackage }
defer resetMock()
t.Run("error_panic", func(t *testing.T) {
// restore at each test to be sure
resetMock()
MyFuncFromExternalPackage = func() error {
return errors.New("test")
}
assert.Panics(t, MyFunc)
})
}
When to use function expression rather than function declaration in Go?: https://stackoverflow.com/questions/46323067/when-to-use-function-expression-rather-than-function-declaration-in-go
Testing os.Exit scenarios in Go with coverage information (coveralls.io/Goveralls): https://stackoverflow.com/questions/40615641/testing-os-exit-scenarios-in-go-with-coverage-information-coveralls-io-goverall/40801733#40801733