Go Pattern: Test Rig Func

Often we need per-test setup and teardown. This is especially useful for third party integration testing, for e.g. testing against a database.

It looks fairly simple. Extending the example from a previous post: Nifty Go Pkg: ory/dockertest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func redisRig(t *testing.T, fn func(rdb *redis.Client)) {

	var err error
	pool, err := dockertest.NewPool("")
	assert.Nil(t, err)

	resource, err := pool.Run("redis", "5.0.6", nil)
	assert.Nil(t, err)

	var rdb *redis.Client
	err = pool.Retry(func() error {
		rdb = newRedisClient(resource.GetPort("6379/tcp"))
		return rdb.Ping().Err()
	})
	assert.Nil(t, err)

	defer rdb.Close()

	// run even if the test fails
	defer func() {
		// remove resources
		err = pool.Purge(resource)
		assert.Nil(t, err)
	}()
	// call test function
	fn(rdb)
}

Use it in a test.

1
2
3
4
5
6
7
8
9
func TestService(t *testing.T) {
    redisRig(t, func(rdb *redis.Client) {
            svc, err := newService(rdb)
            if err != nil {
                t.Errorf("new service err %v",err)
                return
            }
        })
}

The catch here is to use T.Errorf or similar, so that we don’t fatally exit the test using t.Fatal. If we exit fatally, cleanup code won’t run:

1
2
3
4
5
6
	// run even if the test fails
	defer func() {
		// remove resources
		err = pool.Purge(resource)
		assert.Nil(t, err)
	}()

T.Errorf internally calls T.Fail which fails the test but execution continues.

This pattern can be used for: setup/teardown databases, constructing clients to third party services etc.