gomega icon indicating copy to clipboard operation
gomega copied to clipboard

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

Open rickyson96 opened this issue 1 year ago • 2 comments

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!

rickyson96 avatar Feb 01 '23 16:02 rickyson96

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.

onsi avatar Feb 01 '23 18:02 onsi

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"}))

rickyson96 avatar Feb 01 '23 18:02 rickyson96