google / go-cmp

Package for comparing Go values in tests
BSD 3-Clause "New" or "Revised" License
4.17k stars 209 forks source link

How to implement jests.js's `expect.any(String)` like functionality? #351

Closed safareli closed 8 months ago

safareli commented 8 months ago

I'm comparing 2 maps one is from http response and one expected response. As response contains id which is random I want to ignore that property, of course I can delete that property from response and do comparison that way but there are other similar usecases, what if we have id's deeper in an object. deleting things is cumbersome . I want to basically implement somehow some value anyString such that this will "pass":

diff(
  map[string]any{
    "fname":  "jo",
    "lname":  "do,
    "id": "asd-ad12-3rfs-ad4"
  },
  map[string]any{
    "fname":  "jo",
    "lname":  "do,
    "id": anyString
  },
)

Jest.js has something similar expect.any(constructor)

expect(val).toEqual({
  data: expect.any(Array),
  active: true,
  ID: expect.any(String),
})

This expect.any(constructor) returns special value that is used during diffing to only check constructors of values.

I tried to implement anyString:

type AnyString struct {
    value   string
    isInput bool
}

var anyString = AnyString{}

func TestAnyString(t *testing.T) {
    // Example usage
    x := map[string]any{
        "foo": "12",
    }
    y := map[string]any{
        "foo": anyString,
    }

    optTrans := cmp.Transformer("strinany", func(input string) AnyString {
        return AnyString{value: input, isInput: true}
    })
    optComp := cmp.Comparer(func(x, y AnyString) bool {
        // if one is input string and one is anyString we match
        if x.isInput == !y.isInput {
            return true
        }

        return x == y
    })

    if diff := cmp.Diff(x, y, optTrans, optComp); diff != "" {
        fmt.Printf("Mismatch (-x +y):\n%s", diff)
        t.Fail()
    } else {
        fmt.Println("Match")
    }
}

but I am getting:

  map[string]any{
-   "foo": string("12"),
+   "foo": testz.AnyString{},
  }

as I understand the transformation only work if both values on each side are of same type.

Is there any way I can implement that with this library?

Related issues

https://github.com/google/go-cmp/issues/270 https://github.com/google/go-cmp/issues/261

safareli commented 8 months ago

Well actually thanks to example https://play.golang.org/p/iRvh9tTJk_q shared by @dsnet here https://github.com/google/go-cmp/issues/261#issuecomment-844562075 I was able to do what I wanted.

type AnyString struct {
    value   string
    isInput bool
}

var anyString = AnyString{}

func TestAnyString(t *testing.T) {
    // Example usage
    x := map[string]any{
        "foo": "12",
    }
    y := map[string]any{
        "foo": anyString,
    }

    tr := cmp.FilterValues(func(x, y interface{}) bool {
        _, okX1 := x.(string)
        _, okX2 := x.(AnyString)
        _, okY1 := y.(string)
        _, okY2 := y.(AnyString)
        return (okX1 || okX2) && (okY1 || okY2)
    }, cmp.Transformer("AnyStringTransformer", func(x interface{}) AnyString {
        if is, ok := x.(string); ok {
            return AnyString{value: is, isInput: true}
        }
        return x.(AnyString)
    }))

    optComp := cmp.Comparer(func(x AnyString, y AnyString) bool {
        // if one is input string and one is anyString we match
        if x.isInput == !y.isInput {
            return true
        }

        return x == y
    })

    if diff := cmp.Diff(x, y, tr, optComp); diff != "" {
        fmt.Printf("Mismatch (-x +y):\n%s", diff)
        t.Fail()
    } else {
        fmt.Println("Match")
    }
}