uber-go / mock

GoMock is a mocking framework for the Go programming language.
Apache License 2.0
1.85k stars 106 forks source link

Add matcher for structs with ignored order in nested fields #71

Open sebastianbuechler opened 10 months ago

sebastianbuechler commented 10 months ago

In PR https://github.com/golang/mock/pull/546 we got a matcher that ignores the order of a slice or array. Sometimes the order can't be controlled properly it makes sense to have this matcher. But it is only capable of ignoring the order if the struct is of type slice or array, and not nested.

I would propose to add a matcher that will return true for unordered nested structs as well. Example unit test:

func TestOrderlessMatcher(t *testing.T) {
    testCases := []struct {
        expected interface{}
        value    interface{}
        matches  bool
    }{
        {
            struct{ List []string }{List: []string{"Value1", "Value2"}},
            struct{ List []string }{List: []string{"Value2", "Value1"}},
            true,
        },
    }
    for i, testCase := range testCases {
        t.Run(fmt.Sprintf("Test #%d", i), func(t *testing.T) {
            assert := assert.New(t)
            m := matchers.NewOrderlessMatcher(testCase.expected)
            assert.Equal(testCase.matches, m.Matches(testCase.value))
        })
    }
}
r-hang commented 10 months ago

This is an understandable request. We are currently looking into whether or not the form of this feature should be a new top-level matcher or an option or change in the behavior of the existing InAnyOrder matcher.

abhipranay commented 3 months ago

@sebastianbuechler Can you clarify further about the matcher requirements ?

Can the struct have more fields which are not slice/array ? When you say nesting then do you mean any complex nested structure ?

eg:

type A struct {
  Field1 []string{}

  // some customer type which can have slice/array as it's own fields
  Field2 B

  // some primitive type fields
  Field3 int 

  // slice/array of some non primitive type
  Field4 []C
}

type B struct {
  Field1 []D
}

type C struct {
  Field1 int
}

type D struct {
  Field1 string
}

// so are you expecting structs mentioned below should match ?
A1 := A{
  Field1: { "1", "2" },
  Field2: B{
    Field1: []D{
      { Field1: "a" }
      { Field2: "b" }
    }
  },
  Field3: 4,
  Field4: []C{
    { Field1: 1 }
    { Field1: 2 }
  }
}

A2 := A{
  Field1: { "2", "1" },
  Field2: B{
    Field1: []D{
      { Field1: "b" }
      { Field2: "a" }
    }
  },
  Field3: 4,
  Field4: []C{
    { Field1: 1 }
    { Field1: 2 }
  }
}

One can always write a custom matcher for complex object but matching complex structs by ignoring order of slice in them can be helpful as many times using a slice is more appropriate to keep a collection. But slices have property to match only if all elements are equal with order , users end up writing customer matchers.

So having a matcher for complex structure which just works by ignoring any ordering in slice/array fields can be helpful if provided by library.

wdyt @r-hang ?