Can I do value matching on a map in V4 pact-js wit...
# pact-js
k
Can I do value matching on a map in V4 pact-js with pact-jest? I mean, does V4 have something like this:
MatchersV3.eachValueMatches
Or, can I use this in a v4 pactWith?
I saw a builder method in an example that used the chained syntax approach to pass a builder function. But that function isn't available in v4 and the MatchersV3.eachValueMatches doesn't pass type checking in V4.
m
It should work with the V4 builder. What error are you getting?
k
I had type issues. The matcher is not of type AnyMatcher, or whatever the union matcher type's name is. (I'm away from work pc atm)
I am using pact-jest's v4 pactWith function. Then in beforeEach calling provider.addInteraction (provider being the Pact from pactWith). The example I saw uses
Copy code
.addInteraction()
....
.withResponse(200, (builder)=>{})
But I suspect that's v3 pactWith, while v4's addInteraction only accepts an object for body.
Failing approach using v4 pactWith:
Copy code
import { Matchers, MatchersV3 } from '@pact-foundation/pact'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render } from '@testing-library/react'
import { pactWith } from 'jest-pact'

import FilterPane from '@/modules/properties/filtering/FilterPane'

const queryClient = new QueryClient()
const originalFetch = fetch

pactWith(
  {
    consumer: 'environment-comparison-application',
    provider: 'environment-comparison-service',
  },
  (provider) => {
    beforeAll(() => {
      const { port } = provider.opts
      jest.spyOn(global, 'fetch').mockImplementation((input, init) => {
        const url = typeof input === 'string' ? input : (input as Request).url
        if (url.startsWith('/api')) {
          return originalFetch(`<http://localhost>:${port}${input}`, init)
        }
        return originalFetch(input, init)
      })
    })

    afterAll(() => {
      jest.restoreAllMocks()
    })

    beforeEach(async () => {
      await provider.addInteraction({
        uponReceiving: 'a request for supported items',
        withRequest: {
          method: 'GET',
          path: '/api/supported',
        },
        willRespondWith: {
          status: 200,
          body: {
            itemGroups: {
              'Database Tables': Matchers.eachLike('Table1', { min: 1 }),
              'Property Files': Matchers.eachLike('Service1', { min: 1 }),
              Environments: Matchers.eachLike('Env1', { min: 1 }),
            },
            environmentSupport: MatchersV3.eachValueMatches(
              {
                Env1: ['Service1'],
              },
              MatchersV3.eachLike('Service1', 1)
            ),
          },
        },
      })
    })

    describe('returns a list of supported items', async () => {
      const { findByText } = render(
        <QueryClientProvider client={queryClient}>
          <FilterPane />
        </QueryClientProvider>
      )

      const env1 = await findByText('ENV1')
      expect(env1).toBeInTheDocument()

      const service1 = await findByText('Service1')
      expect(service1).toBeInTheDocument()

      const table1 = await findByText('Table1')
      expect(table1).toBeInTheDocument()
    })
  }
)
The ts error on body:
Copy code
Type '{ itemGroups: { 'Database Tables': ArrayMatcher<string[]>; 'Property Files': ArrayMatcher<string[]>; Environments: ArrayMatcher<string[]>; }; environmentSupport: MatchersV3.RulesMatcher<...>; }' is not assignable to type 'AnyTemplate | undefined'.
  Types of property 'environmentSupport' are incompatible.
    Type 'RulesMatcher<string[]>' is not assignable to type 'string | number | boolean | JsonArray | JsonMap | Matcher<AnyTemplate> | ArrayMatcher<AnyTemplate> | TemplateMap | ArrayTemplate | null | undefined'.
      Type 'RulesMatcher<string[]>' is not assignable to type 'TemplateMap'.
        Index signature for type 'string' is missing in type 'RulesMatcher<string[]>'.ts(2322)
Working approach using V3 pactWith:
Copy code
import { Matchers, MatchersV3 } from '@pact-foundation/pact'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render } from '@testing-library/react'
import { pactWith } from 'jest-pact/dist/v3'

import FilterPane from '@/modules/properties/filtering/FilterPane'

const queryClient = new QueryClient()
const originalFetch = fetch

const mockFetch = (port: number) => {
  jest.spyOn(global, 'fetch').mockImplementation((input, init) => {
    const url = typeof input === 'string' ? input : (input as Request).url
    if (url.startsWith('/api')) {
      return originalFetch(`<http://localhost>:${port}${input}`, init)
    }
    return originalFetch(input, init)
  })
}

pactWith(
  {
    consumer: 'environment-comparison-application',
    provider: 'environment-comparison-service',
  },
  (interaction) => {
    interaction('has a list of supported items', ({ provider, execute }) => {
      beforeAll(() => {})

      afterAll(() => {
        jest.restoreAllMocks()
      })

      beforeEach(async () => {
        await provider.addInteraction({
          uponReceiving: 'a request for supported items',
          withRequest: {
            method: 'GET',
            path: '/api/supported',
          },
          willRespondWith: {
            status: 200,
            body: {
              itemGroups: {
                'Database Tables': Matchers.eachLike('Table1', { min: 1 }),
                'Property Files': Matchers.eachLike('Service1', { min: 1 }),
                Environments: Matchers.eachLike('Env1', { min: 1 }),
              },
              environmentSupport: MatchersV3.eachValueMatches(
                {
                  Env1: ['Service1'],
                },
                MatchersV3.eachLike('Service1', 1)
              ),
            },
          },
        })
      })

      describe('returns a list of supported items', () => {
        execute('api call', async (mockserver) => {
          mockFetch(mockserver.port)
          const { findByText } = render(
            <QueryClientProvider client={queryClient}>
              <FilterPane />
            </QueryClientProvider>
          )

          const env1 = await findByText('ENV1')
          expect(env1).toBeInTheDocument()

          const service1 = await findByText('Service1')
          expect(service1).toBeInTheDocument()

          const table1 = await findByText('Table1')
          expect(table1).toBeInTheDocument()
        })
      })
    })
  }
)
I guess a better question would have been to ask where to get the v4 builder, but now I see it isn't available in the Pact object given by pactWith.
m
ah, sorry I don’t know much about jest-pact
I think it’s largely irrelevant now with the latest version of Pact JS
y
jest pact needs to expose the v4 pact instance, it doesn’t currently. its also arguable as to whether the wrapper is needed anymore as we don’t have the same lifecycle concerns introduced by the ruby core using subprocesses. tl;dr - use pact js or fork jest pact and add v4 interface
nod hmm yes 1