I want to be able to compare two dictionaries while ignoring the value of some fields. This is extremely easy in Python with mock.ANY
.
from unittest.mock import ANY
actual = {'userName':'bob', 'lastModified':'2012-01-01'}
expected = {'userName':'bob', 'lastModified': ANY}
assert actual == expected
Here's my attempt in Go that leverages cmp
which allows you to implement a Equal
method (https://pkg.go.dev/github.com/google/go-cmp/cmp#Equal) but it doesn't work. I believe it's because my MatchAll
struct is not "assignable" to string. Is there anyway to get around this?
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
)
type MatchAll string
func (m MatchAll) Equal(v string) bool {
return true
}
func main() {
matchAll := MatchAll("bruh")
actual := map[string]any{
"key": matchAll,
}
expected := map[string]any{
"key": "foobar",
}
fmt.Println(cmp.Equal(expected, actual))
}
Looking at the cmp
code, I believe the problem here is that when comparing map elements, the type that's used to check for the existence of the Equal
method is the element type of the map (here, any
), not the concrete type of one of the two values (here, string
or MatchAll
). Since the any
type does not declare an Equal
method, it falls back to the built-in comparisons.
As an alternative, you could potentially use the cmp.FilterValues and cmp.Comparer functions to construct an option that supports your special "equal-to-anything" value. For example, this would appear to work as intended:
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
)
type matchAllType struct{}
var matchAll = matchAllType{}
var matchAllOption = cmp.FilterValues(
// Filter function: returns `true` only for values using this comparison.
func(x, y any) bool {
return x == matchAll || y == matchAll
},
// Comparison function: called for pairs of values the filter accepts.
cmp.Comparer(func(x, y any) bool { return true }),
)
func main() {
actual := map[string]any{
"key": matchAll,
}
expected := map[string]any{
"key": "foobar",
}
fmt.Println(cmp.Equal(expected, actual, matchAllOption))
}
The always-true comparison function could possibly be replaced with cmp.Ignore() as well, though I'm not entirely sure whether the semantics are exactly the same. It does work in the above example, at least.
There is also the cmpopts.IgnoreMapEntries option, which could be used as follows, but with slightly different semantics:
var matchAllOption = cmpopts.IgnoreMapEntries(func(_, v any) bool { return v == matchAll })
The difference is that with this option, the comparison would be true
even if the actual
dictionary did not contain an entry with a matching key at all, because the built-in map comparison tests whether "recursively calling Equal on all non-ignored map entries report equal", emphasis added. By contrast, the earlier solutions still require the other map to contain some value with the same key.