How I mock time and other static functions in Golang

The problem

In unit testing in Go, I’ve run into situations where a function depends on a calling time.Now(), but I need to specify the set the time as part of the test. In general, there’s three ways of solving this.

  1. Inject the time as a function parameter in the calling function. This works, but doesn’t help when running integration tests, as you need to keep injecting the time further and further up the stack.
  2. Inject the time via a time service. This is a variation of the above solution, with a time service passed around that includes other date methods. It suffers from the same problems.
  3. Monkey patch the test using monkey as described here. This frankly strikes me as a bit janky, and is not guaranteed to last as a solution.
  4. Alias time.Now() using a package function var. To see this in action, read on!

Solution

Take the following program. How would we test getNow()?

package main

import "time"

func main() {
	println(getNow())
}

func getNow() string {
	return time.Now().Format(time.RFC1123Z)
}

Modify it so time.Now() is by default a function pointer to the real function, so…

package main

import "time"

var now = time.Now // Note the lack of ().

func main() {
	println(getNow())
}

func getNow() string {
	return now().Format(time.RFC1123Z) // Call our var on line 5 as a function.
}

That’s it.

Testing is straight forward.

package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
	"time"
)

func Test_getNow(t *testing.T) {
	now = func() time.Time { // Override the time variable with a static result, but only for this test.
		return time.Date(2024, 12, 31, 15, 16, 17, 0, time.UTC)
	}
	actual := getNow()
	assert.Equal(t, "Tue, 31 Dec 2024 15:16:17 +0000", actual)
}

During production, main.now is always initiated to the system default.

Other Uses

This also lets you mock New*() functions provided by SDKs that without adding yet another layer.

For example, the following allows cleanly mocking out the slack.New() SDK function without creating yet another layer.

var SlackNew = func(token string, options ...slack.Option) SlackApiInterface {
        return slack.New(token, options...)
}