Jakub Jarosz

Security, Systems & Electrical Automation

Simplify Go Tests with cmp

2026-01-15 Go

From 50 Go Testing Mistakes

In the last two articles: Simplify and clarify and Making tests tidy, we looked at a few test examples taken from OSS code - the cache storages project used by the Souin HTTP cache.

We refactored tests, made them more readable and robust. The tests now:

  • use test helpers for setting up preconditions
  • check preconditions before executing test logic
  • use meaningful variable names

Today, we will make the last change to simplify checking test results.

Comparing want with got

The original test in Listing 1 checks whether the res value (a slice of bytes) has a length of 0. If it is, it means the res is not what we added to the store. Next, the res is converted from a slice of bytes to a string, and compared with the constant baseValue. And again, if both values differ, the test fails.

func TestIShouldBeAbleToReadAndWriteDataInSimplefs(t *testing.T) {
	client, _ := getSimplefsInstance()

	_ = client.Set("Test", []byte(baseValue), time.Duration(20)*time.Second)
	time.Sleep(1 * time.Second)

	res := client.Get("Test")
	if len(res) == 0 {
		t.Errorf("Key %s should exist", baseValue)
	}

	if baseValue != string(res) {
		t.Errorf("%s not corresponding to %s", string(res), baseValue)
	}
}

Listing 1

As always, it’s time to ask: what are we really testing here? What functionality are we validating?

The business logic we test here is storing and retrieving some value. What we really expect from the system is to get what we stored.

Imagine you buy a new car and park it in the garage. Your home security cameras keep an eye on it 24h. When you look at the CCTV monitor, you expect to see the same car, your car. You don’t need to look at it and check whether the colour is the same, whether the tyres are in place, etc.

The same logic applies to our stored slice of bytes. If the slice is zero, it means it’s not the value we stored. If the stored bytes are not the same, it means it’s not the value we stored, and so on. We don’t need to verify all properties of the stored item separately to determine whether it’s the same item we want.

cmp to the rescue

Instead of checking the properties of the value one by one, we will use the cmp package. The cmp.Equal method does all that we want. It takes the value we want and the value we got and compares them. The result is binary. Either the values are equal or not.

Here is the part of our test waiting for the change.

got := client.Get("Test")
if len(got) == 0 {
	t.Errorf("Key %s should exist", baseValue)
}

if baseValue != string(got) {
	t.Errorf("%s not corresponding to %s", string(res), baseValue)
}

Both if blocks are replaced with a single equality check.

got := store.Get("key")
if !cmp.Equal([]byte("123"), got) {
	t.Error("stored and retrieved values don't match")
}

In case of an error, we see a clear message indicating the issue.

The final test version in Listing 2 looks cleaner. cmp.Equal checks the equality of the two values and reports the final verdict. That’s precisely what we want to know: if the value we set in the store is really the one we get.

func TestSetAndRetrieveValueFromStore(t *testing.T) {
	store := newDefaultStore(t)

	err := store.Set("key", []byte("123"), time.Duration(20)*time.Second)
	if err != nil {
		t.Fatal(err)
	}
	time.Sleep(1 * time.Second)

	got := store.Get("key")
	if !cmp.Equal([]byte("123"), got) {
		t.Error("stored and retrieved values don't match")
	}
}

Listing 2

Clear and simple approach recommended by Go authors:

Use the cmp package. Use cmp.Equal for equality comparison and cmp.Diff to obtain a human-readable diff between objects.

The cmp package is the only package outside the Go std library that we need to test and document how the system behaves. But to ensure we describe tested behaviour, it’s good to run all the tests with gotestdox. So, what do your tests look like? Does everyone on your team understand what they test?