onsi / gomega

Ginkgo's Preferred Matcher Library
http://onsi.github.io/gomega/
MIT License
2.16k stars 281 forks source link

Is there any support for matching the rest of the key for map? #633

Open rickyson96 opened 1 year ago

rickyson96 commented 1 year ago

Hi!

My usecase for this is to have a default matching value for map's key.

tc := map[string][]string{
  "test_key":  {"aaa"},
  "other_key": {},
  "ignore":    {},
}

Expect(tc).To(HaveKeyWithValue("test_key", "aaa"))

Now, the test case is that, other than test_key, it should be empty or not exists. if I try something like this Expect(tc).To(HaveKeyWithValue(Not(Equal("test_key")), BeEmpty())), it will not match if there is no other key than the test key.

Currently I'm using this matcher for that case

Expect(tc).To(WithTransform(
  func(m map[string]string) map[string]string {
    delete(m, "test_key")
    return m
  },
  Or(BeEmpty(), HaveEach(BeEmpty())),
))

Currently, I'm doing this for a composed matcher, so, that solution seems good enough. But I think I'll ask anyway to see if there is anything I miss or that might be a possible improvement.

Thanks!

onsi commented 1 year ago

hey is this a one-off? or are you wanting a reusable matcher that will allow you to make this assertion all over the place?

If it's a one off I'd keep it simple:

Expect(tc).To(HaveKeyWithValue("test_key", "aaa"))
for k, v := range tc {
    if k != "test_key" {
        Expect(tc).To(HaveKeyWithValue(k, BeEmpty()))
    }
}

If you want something reusable I can share a few ideas. There are lots of options, including a simple helper function:

func MapHasKeyWithValueAndOthersEmpty(tc map[string][]string, key string, value, string) {
    GinkgoHelper()
    Expect(tc).To(HaveKeyWithValue(key, value))
    for k, v := range tc {
        if k != key {
            Expect(tc).To(HaveKeyWithValue(k, BeEmpty()))
        }
    }
}

which lets you just write

MapHasKeyWithValueAndOthersEmpty(tc, "test_key", "aaa")

in your specs.

Can also show you what it looks like to make a custom matcher for this using gcustom, or to have a matcher that wraps gstruct.

rickyson96 commented 1 year ago

Actually, I'm writing custom matcher so that I can abstract away the matcher's details.

type MyStruct struct {
    ValuesMap map[string][]string
}

func HavePendingValue(key string, pendingValues []string) types.GomegaMatcher {
    return WithTransform(
        func(s MyStruct) map[string][]string {
            return s.ValuesMap
        },
        SatisfyAll(
            HaveKeyWithValue(key, ConsistOf(pendingValues)),
            WithTransform(func(pv map[string][]string) map[string][]string {
                delete(pv, key)
                return pv
            }, Or(BeEmpty(), HaveEach(BeEmpty()))),
        ),
    )
}

// and on the test case
Expect(myStruct).To(HavePendingValue("test_key", []string{"abc"}))