1

Is there an easy and compact way using Testify to assert that a slice of pointers to strings contains a pointer to a string that matches my expectation?

Imagine that you're getting a slice of pointers to strings back from a function call (maybe from an API), and you'd like to validate that it contains pointers to the strings that you'd expect. To simulate that, I'll just make a test data structure to illustrate my point:

// Shared Fixture
var one = "one"
var two = "two"
var three = "three"

var slice = []*string{&one, &two, &three}

Now I want to write a test that asserts the slice contains an expected value. I could write this test:

func TestSliceContainsString(t *testing.T) {
    assert.Contains(t, slice, "one")
}

It doesn't work: []*string{(*string)(0x22994f0), (*string)(0x2299510), (*string)(0x2299500)} does not contain "one". Makes sense, the slice contains pointers to strings, and the string "one" is not one of those pointers.

I could convert it first. It takes more code, but it works:

func TestDereferencedSliceContainsString(t *testing.T) {
    deref := make([]string, len(slice))
    for i, v := range slice {
        deref[i] = *v
    }
    assert.Contains(t, deref, "one")
}

I can also pass a pointer to a string as my expectation:

func TestSliceContainsPointerToExpectation(t *testing.T) {
    expect := "one"

    assert.Same(t, &one, &one)
    assert.NotSame(t, &one, &expect)

    // How can I assert that they contain values
    assert.Contains(t, slice, &expect)
}

Honestly, that's not bad. I can assert that a reference to a string (pointing to a difference memory location) contains the value that I expect. The main annoyance with this path is that I can't pass a reference to a literal, which would make it take less space:

func TestSliceContainsString(t *testing.T) {
    assert.Contains(t, slice, &"one")
}

Is there another approach that I'm not considering? Is one of these more idiomatic of golang/testify?

Geoffrey Wiseman
  • 5,459
  • 3
  • 34
  • 52
  • 1
    I think you've listed all the options there. I took a brief (non-exhaustive) look at the Testify source code. It looks that `Contains` is using [reflect.DeepEqual](https://pkg.go.dev/reflect#DeepEqual) for comparison. As stated in the docs for `reflect`, this will consider any values with different types to not be equal. For my part, in tests I usually would use the second way you suggested (`Contains` with a string pointer). – phonaputer Jun 20 '22 at 22:25

1 Answers1

1

Yes, unfortunately the &"one" syntax isn't valid (a few years ago, I opened an issue to allow that syntax; it was closed, though Rob Pike opened a similar issue more recently).

For now, I think the best approach is to just take the address of a variable, as in your TestSliceContainsPointerToExpectation. Or, if you're doing this often, you can write a simple stringPtr function so you can do it as a one-liner:

func stringPtr(value string) *string {
    return &value
}

func TestSliceContainsString(t *testing.T) {
    assert.Contains(t, slice, stringPtr("one"))
}

Or, if you're using at least Go 1.18 (with generics), you can make a generic ptr function:

func ptr[T any](value T) *T {
    return &value
}

func TestSliceContains(t *testing.T) {
    assert.Contains(t, slice, ptr("one"))
}

See these in the Go Playground.

Ben Hoyt
  • 10,694
  • 5
  • 60
  • 84