Good morning! I've been having issues with trying...
# pact-js
a
Good morning! I've been having issues with trying to pact test an endpoint with a query param in jest.
Copy code
describe('Pact test', () => {
  it('should work', async () => {
    const apiPath = '/userprofile/v1/paymentmethods';
    const interaction: InteractionObject = {
      state: 'Client has submitted a get all payment methods request',
      uponReceiving: 'a valid payment methods get body',
      withRequest: {
        method: 'GET',
        path: '/userprofile/v1/paymentmethods',
        query: { userId: 11111 },
      },
...
When I go to the Provider contract on Pactflow, it shows that the query param is there (in screenshot) However, when I run the pact test, it gives me the 1st error
Could not find key "userId" in empty Hash at $.query
I've tried multiple variations, such as removing the query property and placing it directly in the path and turning the userId into a string. When I change both types into a string, it gives me the 2nd error
y
Hey, I assume this is using bi-directional contract testing, as you are mentioning a provider contract? on your consumer side, is it failing to generate a consumer pact?
a
Yes, I believe so
y
can you show the rest of your pact test, where you are invoking your client under test, and the code for the client under test.
a
Copy code
/* eslint-disable import/no-extraneous-dependencies */
import { pactWith } from 'jest-pact';
import {
  InteractionObject, Matchers, Publisher,
} from '@pact-foundation/pact';
import fetch from 'node-fetch';

const opts = {
  pactFilesOrDirs: ['pact/pacts'],
  pactBroker: '<https://uship.pactflow.io/>',
  pactBrokerToken: process.env.PACTFLOW_SECRET_TOKEN,
  consumerVersion: process.env.GIT_COMMIT,
  publishVerificationResult: true,
  branch: process.env.GIT_BRANCH,
  tags: ['tag'],
};

pactWith(
  {
    consumer: 'consumer',
    provider: 'provider',
    pactfileWriteMode: 'overwrite',
  },
  async (provider: any) => {
    const client = (path, options?) => {
      const url = `${provider.mockService.baseUrl}${path}`;
      return fetch(url, options);
    };

    afterAll(async () => {
      await new Publisher(opts).publishPacts();
    });

    describe('Payment methods', () => {
      it('should be able to create a pact for get all payment methods', async () => {
        const apiPath = '/userprofile/v1/paymentmethods';
        const interaction: InteractionObject = {
          state: 'Client has submitted a get all payment methods request',
          uponReceiving: 'a valid payment methods get body',
          withRequest: {
            method: 'GET',
            path: '/userprofile/v1/paymentmethods',
            query = { userId: '111' }
          },
          willRespondWith: {
            headers: {
              'Content-Type': 'application/json',
            },
            body: {
              paymentMethods: [{
                id: Matchers.like(73),
                userId: Matchers.like(44441),
                externalId: Matchers.like('id_123'),
                type: Matchers.like('card'),
                lastFour: Matchers.like('4242'),
                isPrimary: Matchers.like(true),
                issuer: Matchers.like('Visa'),
                verificationStatus: Matchers.like('notapplicable'),
                expiration: {
                  year: Matchers.like(2024),
                  month: Matchers.like(7),
                },
              },
              {
                id: Matchers.like(73),
                userId: Matchers.like(44441),
                externalId: Matchers.like('id_123'),
                type: Matchers.like('payonterms'),
                lastFour: null,
                isPrimary: Matchers.like(true),
                issuer: null,
                verificationStatus: Matchers.like('notapplicable'),
                creditLimit: {
                  creditApproved: {
                    amount: Matchers.like(50000),
                    currency: Matchers.like('USD'),
                  },
                  creditAvailable: {
                    amount: Matchers.like(38159.03),
                    currency: Matchers.like('USD'),
                  },
                  creditUsed: {
                    amount: Matchers.like(11840.97),
                    currency: Matchers.like('USD'),
                  },
                },
              },
              {
                id: Matchers.like(73),
                userId: Matchers.like(44441),
                externalId: Matchers.like('id_123'),
                type: Matchers.like('ACH'),
                lastFour: Matchers.like('6789'),
                isPrimary: Matchers.like(true),
                issuer: Matchers.like('Visa'),
                verificationStatus: Matchers.like('notapplicable'),
              },
              ],

            },
            status: 200,
          },
        };
        await provider.addInteraction(interaction);
        const response = await client(apiPath, {
          method: 'get',
          headers: { 'Content-Type': 'application/json' },
        });
        expect(response.status).toBe(200);
      });
    });
  },
);
y
So just to note there you aren't actually testing your client api layer that is calling your provider, is that intended
Copy code
const client = (path, options?) => {
      const url = `${provider.mockService.baseUrl}${path}`;
      return fetch(url, options);
    };
you aren't passing query params to the client, that is using
fetch
under the hood
Copy code
const response = await client(apiPath, {
          method: 'get',
          headers: { 'Content-Type': 'application/json' },
        });
☝️ 1
you should be testing your API layer, such as https://github.com/pact-foundation/jest-pact#usage
a
Hmmm, I'm not quite sure what not testing the client api layer means. I was taking over for someone who worked on this and they're off on vacation. Do you mean are we intending not actually test the endpoint with fake data?
y
in your code base there will be an api client making a request to a provider, this test shown, isn't testing that bit of code
so you have no guarantee of drift
a
What is drift?
y
where the expectation on the provider/consumer do not match up, and fail to communicate when deployed
this example from the readme explains it well I think https://github.com/pact-foundation/jest-pact#usage better than I am at the moment 🙂 just about to go into a meeting for now
tl;dr to get your test to pass you need to read up on how to pass query parameters to fetch
longer read, the test isn't that useful and should be updated to test your consumer client (that makes the calls to the provider)
a
Thank you, I'll take a read!
y
So, In your test, the client under test shown below, should be the actual client in your consumer code that is making the call to the
provider
service
Copy code
const client = (path, options?) => {
      const url = `${provider.mockService.baseUrl}${path}`;
      return fetch(url, options);
    };
when you call that client in your test, it is passed the
apiPath
Copy code
const apiPath = '/userprofile/v1/paymentmethods';
and has no knowledge of the query parameters you have defined in the Pact interaction
Copy code
const response = await client(apiPath, {
          method: 'get',
          headers: { 'Content-Type': 'application/json' },
        });
If you wanted to pass headers to fetch, there is an example on the fetch website https://fetch.spec.whatwg.org/#fetch-api
Copy code
var url = new URL("<https://geo.example.org/api>"),
    params = {lat:35.696233, long:139.570431}
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
fetch(url).then(/* … */)
The errors shown by Pact, are saying that the client you have setup is making a request to
/userprofile/v1/paymentmethods
but has no query parameters, which is exactly what is being demonstrated by the code shown. Also additional point to note, as you mentioned a provider contract, I am assuming we are using Bi-Directional contract testing to validate the consumer/provider contracts. https://docs.pactflow.io/docs/bi-directional-contract-testing If so, the
Matchers
used in this pact test, will be persisted to a Pact contract file but will be ignored at verification time. Matching is done based on the type specified in the pact file example https://docs.pactflow.io/docs/bi-directional-contract-testing/compatibility-checks so this
id: Matchers.like(73)
can just be
id: 73
and it will expect the field
id
to exist and be a
number
The matchers are relevant when using consumer driven contract testing, where you are using provider verification driven by the Pact framework https://docs.pact.io/getting_started/how_pact_works#provider-verification
a
Is the query param type on the provider side able to be either string or number, or can it only be string?
y
your spec is expecting integer, so I would expect it to fail if you provide a string value
m
So this looks to be a consumer test. This screenshot shows the problem - you’re not sending the query along with the call in that test as Yousaf has pointed out. Pact is a unit testing tool that you use to test your API client behaviour. If you don’t use your actual API client then it’s not a useful test because if the behaviour of the actual API client changes, this test won’t pick the problem up (this is the “drift” Yousaf is describing). You have a test that is not testing your actual code, so the actual behaviour can drift from your expectations, but you won’t know until it breaks (probably in prod)
t
Query parameters are always strings, because they go in the URL
☝️ 1
m
Is the query param type on the provider side able to be either string or number, or can it only be string?
For clarity, what Tim said