Comparing and Diffing Objects in Go Tests

Tuesday, June 8, 2021

A conversation came up at work about how people preferred to compare and diff objects when writing go tests. I was assigned to do a quick comparison.

Given the following inputs:

package main

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/go-test/deep"
	"github.com/google/go-cmp/cmp"
)

type MyStruct struct {
	a []string
	b string
	c bool
	d *SubStruct
	e map[string]string
}

type SubStruct struct {
	a string
}

var (
	obj1 = MyStruct{
		a: []string{"foo", "bar", "baz"},
		b: "red",
		c: false,
		d: &SubStruct{
			"obj1",
		},
		e: map[string]string{
			"France":      "Paris",
			"Germany":     "Berlin",
			"Netherlands": "Amsterdam",
		},
	}

	obj2 = MyStruct{
		a: []string{"foo", "baz"},
		b: "blue",
		c: true,
		d: &SubStruct{
			"obj2",
		},
		e: map[string]string{
			"France":      "Lyon",
			"Germany":     "Bonn",
			"Netherlands": "Den Haag",
			"England":     "Manchester",
		},
	}
)

func main() {
	fmt.Println("=== Stdlib ===\n")

	if !reflect.DeepEqual(obj1, obj2) {
		fmt.Printf("obj1 = %#v;\nobj2 = %#v\n", obj1, obj2)
	}

	fmt.Println("\n=== deep.Equal ===\n")

	deep.CompareUnexportedFields = true
	if diff := deep.Equal(obj1, obj2); diff != nil {
		fmt.Printf("obj1 != obj2:\n%v\n", strings.Join(diff, "\n"))
	}

	fmt.Println("\n=== go-cmp ===\n")

	if diff := cmp.Diff(obj1, obj2, cmp.AllowUnexported(obj1, *obj1.d)); diff != "" {
		fmt.Printf("obj1 != obj2:\n%v\n", diff)
	}
}

Output

=== Stdlib ===

obj1 = main.MyStruct{a:[]string{"foo", "bar", "baz"}, b:"red", c:false, d:(*main.SubStruct)(0x124e350), e:map[string]string{"France":"Paris", "Germany":"Berlin", "Netherlands":"Amsterdam"}};
obj2 = main.MyStruct{a:[]string{"foo", "baz"}, b:"blue", c:true, d:(*main.SubStruct)(0x124e360), e:map[string]string{"England":"Manchester", "France":"Lyon", "Germany":"Bonn", "Netherlands":"Den Haag"}}

=== deep.Equal ===

obj1 != obj2:
a.slice[1]: bar != baz
a.slice[2]: baz != <no value>
b: red != blue
c: false != true
d.a: obj1 != obj2
e.map[France]: Paris != Lyon
e.map[Germany]: Berlin != Bonn
e.map[Netherlands]: Amsterdam != Den Haag
e.map[England]: <does not have key> != Manchester

=== go-cmp ===

obj1 != obj2:
  main.MyStruct{
  	a: []string{
  		"foo",
- 		"bar",
  		"baz",
  	},
- 	b: "red",
+ 	b: "blue",
- 	c: false,
+ 	c: true,
- 	d: &main.SubStruct{a: "obj1"},
+ 	d: &main.SubStruct{a: "obj2"},
  	e: map[string]string{
+ 		"England":     "Manchester",
- 		"France":      "Paris",
+ 		"France":      "Lyon",
- 		"Germany":     "Berlin",
+ 		"Germany":     "Bonn",
- 		"Netherlands": "Amsterdam",
+ 		"Netherlands": "Den Haag",
  	},
  }
go

Navigating Git Branches with FZF