Search code examples
typescriptjestjspact

Comparing object data to multiple example values from pact matcher typescript


I am using pact V3 in typescript to verify a contract with an API. The response example matcher is the following:

const userExample = {
  email: string("foo.bar@example.com"),
  id: number(123)
}

The actual response.data from the API looks like:

{
  "email": "foo.bar@example.com",
  "id": 123
}

When I execute the test:

expect(response.data).toStrictEqual(userExample)

I get that it is comparing the response.data to the Matcher object instead of the actual example values:

contract:test:     - Expected  - 8
contract:test:     + Received  + 2
contract:test: 
contract:test:       Object {
contract:test:     -   "email": Object {
contract:test:     -     "pact:matcher:type": "type",
contract:test:     -     "value": "foo.bar@example.com",
contract:test:     -   },
contract:test:     -   "id": Object {
contract:test:     -     "pact:matcher:type": "number",
contract:test:     -     "value": 123,
contract:test:     -   },
contract:test:     +   "email": "foo.bar@example.com",
contract:test:     +   "id": 123,
contract:test:       }

I can get around this by instead doing:

expect(response.data.email).toStrictEqual(userExample.email.value)
expect(response.data.id).toStrictEqual(userExample.id.value)

But I am trying to figure out if there is a way to compare the entire response without having to specify the .value in a separate expect function line for each potential value of the userExample response?

Full test code below:

import { MatchersV3, PactV3 } from "@pact-foundation/pact"

const { like, atLeastOneLike, string, number } = MatchersV3
const provider = new PactV3({
  consumer: "consumer-service",
  provider: "provider-service"
})

const userExample = {
  email: string("foo.bar@example.com"),
  id: number(123),
}

test("get users", async () => {
  provider.addInteraction({
    states: [{ description: "user exists" }],
    uponReceiving: "get users",
    withRequest: {
      method: "GET",
      path: "/users",
    },
    willRespondWith: {
      status: 200,
      body: like({
        users: atLeastOneLike(userExample),
      }),
    },
  })

  await provider.executeTest(async (mockserver) => {
    const userServiceClient = new UserServiceClient(mockserver.url)
    const response = await userServiceClient.getUsers()

    expect(response.data.email).toStrictEqual(userExample.email.value)
    expect(response.data.id).toStrictEqual(userExample.id.value)
  })
})

Solution

  • You can use the reify function to do this (previously called extractPayload), to remove the matches from the object.

    This being said:

    const userExample = {
      email: string("foo.bar@example.com"),
      id: number(123),
    }
    

    is equivalent to

    const userExample = like({
      email: "foo.bar@example.com",
      id: 123,
    })
    

    That is, a like matcher just matches on types.

    atLeastOneLike is also an extension of this matcher, but for arrays of objects.

    So you could rewrite your test as follows:

    const userExample = {
      email: "foo.bar@example.com",
      id: 123,
    }
    
    test("get users", async () => {
      provider.addInteraction({
        states: [{ description: "user exists" }],
        uponReceiving: "get users",
        withRequest: {
          method: "GET",
          path: "/users",
        },
        willRespondWith: {
          status: 200,
          body: like({
            users: atLeastOneLike(userExample),
          }),
        },
      })
    
      await provider.executeTest(async (mockserver) => {
        const userServiceClient = new UserServiceClient(mockserver.url)
        const response = await userServiceClient.getUsers()
    
        expect(response.data.email).toStrictEqual(userExample.email)
        expect(response.data.id).toStrictEqual(userExample.id)
      })
    })