Home > Software design >  Compare structs that have slice fields ignoring item order with stretchr/testify
Compare structs that have slice fields ignoring item order with stretchr/testify

Time:11-15

I have a problem where I need to compare two very large structs (protobuf generated) with each other, as part of a test-case. These structs have multiple nested arrays in them. Below is a simplified example that reproduces / demonstrates the problem.

package pkg

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

type structOne struct {
    Foo  string
    Offs []*structTwo
}

type structTwo struct {
    Identifier string
}

func Test_Compare(t *testing.T) {
    exp := &structOne{
        Foo: "bar",
        Offs: []*structTwo{
            {
                Identifier: "one",
            },
            {
                Identifier: "two",
            },
            {
                Identifier: "three",
            },
            {
                Identifier: "four",
            },
        },
    }

    act := &structOne{
        Foo: "bar",
        Offs: []*structTwo{
            {
                Identifier: "four",
            },
            {
                Identifier: "three",
            },
            {
                Identifier: "two",
            },
            {
                Identifier: "one",
            },
        },
    }

    assert.Equal(t, exp, act)                   // fails
    assert.True(t, reflect.DeepEqual(exp, act)) // fails
}

I have tried using assert.Equal(t, exp, act) and assert.True(t, reflect.DeepEqual(exp, act)). I am looking for a way to compare such structs, preferably without having to create custom comparison functions for all of objects.

Thank you

CodePudding user response:

You can use assert.ElementsMatch to compare two slices irrespective of element ordering.

ElementsMatch asserts that the specified listA(array, slice...) is equal to specified listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, the number of appearances of each of them in both lists should match.

However this applies only to the slice field itself. If your struct model has few fields, you can compare them one by one and use ElementsMatch on the slice:

    assert.Equal(t, exp.Foo, act.Foo)
    assert.ElementsMatch(t, exp.Offs, act.Offs)

If your structs have a lot of fields, you can reassign the slice values to temp variables, nil the fields out, and then compare:

    expOffs := exp.Offs
    actOffs := act.Offs

    exp.Offs = nil
    act.Offs = nil

    assert.Equal(t, exp, act) // comparing full structs without Offs
    assert.ElementsMatch(t, expOffs, actOffs) // comparing Offs separately

It would be even better if stretchr/testify allowed registering custom comparators for user-defined types, or checking if the objects implement a certain interface and call that to test equality

if cmp, ok := listA.(Comparator); ok { 
    cmp.Compare(listB) 
}

but I'm not aware of such a feature.

CodePudding user response:

Providing an answer to my own question because, it is one approach that works for my case.

Using this library solves the problem: https://github.com/r3labs/diff

Then I can assert the list of changes is empty. A user earlier today had provided this as answer which worked, but the answer was deleted by the user before I could reply to it.

  • Related