Search code examples
unit-testinggotesting

Test callbacks like beforeEach


I want to perform unit tests over a struct that I coded in golang. However I have repetitive tasks that I don't want to explicitly repeat manually every time. Still, I like to put them in a BeforeEach function and then make each test execute it before running without explicitly recalling it within the test. Is there a way to do so?

To give some context take this test as an example. How do I move the setup part to an external function without calling it in every test?

func TestBootstrap_LoadError(t *testing.T) {
    // Setup
    mockedLoader := new(MockLoader)
    env.SetLoader(mockedLoader)

    // Given
    mockedLoader.On("Load", mock.Anything).Return(errors.New("error loading .env file"))
    envService := env.EnvService()

    // Test
    err := envService.Bootstrap()

    // Assertions
    assert.Error(t, err)
    assert.False(t, envService.IsBootstrapped())
}

Solution

  • Global Setup

    Using the standard library testing package there is a special TestMain function that can be declared once per (test) package and is run once for all tests. It has a function to run the tests and can be used to put setup and teardown code for all tests in the package.

    func TestMain(m *testing.M) {
        setup()
    
        // runs all tests in the package
        exitCode := m.Run()
    
        teardown()
    
        os.Exit(exitCode)
    }
    

    One of the disadvantages of the TestMain is that you need to communicate the setup variables (e.g. a mock or real DB connection) to the tests via global test variables. There is not way to pass them to the tests directly.

    Test Setup

    If you want to stick with the standard library having a function that can be called in each test to setup and one for teardown is an option if you need per test setup and teardown. Here variables can easily be passed into and out of the setup / teardown functions.

    func TestExample(t *testing.T) {
        setup()
        defer teardown()
    
        // test code
    }
    

    Other Libraries

    There are other unit test libraries that have more features around setup and teardown. For example the suite package of testify is able to group tests into a suite and have a setup and tear down per suite. Multiple suites can be put into one (test) package which allows for more fine grained setup and teardown control. Additionally it also adds more options apart from just one setup and one teardown function for the entire suite. See docs.

    Variables can be managed per suite and don't pollute the global variable space.


    Usually I stick to what I described in Test Setup section. Having that plus using sub-tests is usually enough control for setup and teardown. See also this answer for a clever way to control setup and teardown per sub-test.