stretchr / testify

A toolkit with common assertions and mocks that plays nicely with the standard library
MIT License
23.15k stars 1.59k forks source link

Add `Subset`/`ElementsMatch` by func assertion #1487

Closed SirSova closed 11 months ago

SirSova commented 11 months ago

In addition to another issue: https://github.com/stretchr/testify/issues/1486, I suggest a new(more complex) assertion similar to Subset/ElementsMatch. So, I face often situations when I need to check the elements, but not by all the properties (partially). For example, the actual objects contain too many fields, but I need to check 2-3 of them in each element. This is what I came to right now:

func SubsetByFn(t, list interface{}, subset interface{}, comp /*func(listElement interface{}, subsetElement interface{}) bool */, interface{}, msgAndArgs ...interface{})

// Simple example:
SubsetByFn(t, []string{"One", "Two", "Three", "Four"}, []int{3, 4}, func(word string, length int) { return len(word) == length }, "should contain words with length 3 and 4")

// Complex example:
type fooBar struct {
    foo string
    bar int
}
type fooBarBuzz struct {
    foo  string
    bar  int
    buzz time.Time
}
SubsetByFn(t,
    []fooBarBuzz{ // it's suppose to be generated and have lots of fields
        {foo: "One", bar: 1, buzz: time.Now()},
        {foo: "Two", bar: 2, buzz: time.Now().Add(1*time.Second)},
        {foo: "Three", bar: 3, buzz: time.Now().Add(10*time.Second)},
        {foo: "Four", bar: 4, buzz: time.Now().Add(1*time.Minute)},
    },
    []fooBar{ // the expected list that contains only fields that we wanna check, can be a struct type generated just for a specific test 
        {foo: "Three", bar: 3},
        {foo: "One", bar: 1},
    },
    func(actual fooBarBuzz, expected fooBar) { // defined comparison callback 
        return actual.foo == expected.foo && actual.bar == expected.bar
    },
    "should contain subset {foo: \"One\", bar: 1} and {foo: \"Three\", bar: 3}"
)

It can be 2 different functions, for Subset + for all elements pass the condition (ElementsMatch alternative)

dolmen commented 11 months ago

Like #1486 this use case can be rewritten using slices.ContainsFunc, but this time by using it at 2 depth levels.

See https://go.dev/play/p/Y6d7YuRjRu1

    expectedValues := []fooBar{ // the expected list that contains only fields that we wanna check, can be a struct type generated just for a specific test
        {foo: "Three", bar: 3},
        {foo: "One", bar: 1},
    }

    assert.True(t,
        slices.ContainsFunc(
            []fooBarBuzz{ // it's suppose to be generated and have lots of fields
                {foo: "One", bar: 1, buzz: time.Now()},
                {foo: "Two", bar: 2, buzz: time.Now().Add(1 * time.Second)},
                {foo: "Three", bar: 3, buzz: time.Now().Add(10 * time.Second)},
                {foo: "Four", bar: 4, buzz: time.Now().Add(1 * time.Minute)},
            },
            func(actual fooBarBuzz) bool {
                return slices.ContainsFunc(expectedValues, func(expected fooBar) bool {
                    return actual.foo == expected.foo && actual.bar == expected.bar
                })
            },
        ),
        "should contain subset {foo: \"One\", bar: 1} and {foo: \"Three\", bar: 3}",
    )

Like #1486 I think that this is more readable than introducing a new method in Testify. So I'm rejecting this feature request.