Hi, all. I have couple of consumers with contract ...
# pact-js
i
Hi, all. I have couple of consumers with contract files generating on them. On my MacOS Ventura on Intel all works fine, but when another person tried to run the same on Ubuntu, he got following errors: For first app:
Copy code
FAIL  pact/consumer/generateContract.pact.ts (6.46 s)


  ● Test suite failed to run

    Error in native callback

      at mockServerMismatches (node_modules/@pact-foundation/pact-core/src/consumer/internals.ts:10:9)
      at Object.mockServerMismatches (node_modules/@pact-foundation/pact-core/src/consumer/index.ts:123:27)
      at PactV3.<anonymous> (node_modules/@pact-foundation/src/v3/pact.ts:206:39)
      at step (node_modules/@pact-foundation/pact/src/v3/pact.js:33:23)
      at Object.throw (node_modules/@pact-foundation/pact/src/v3/pact.js:14:53)
      at rejected (node_modules/@pact-foundation/pact/src/v3/pact.js:6:65)

Test Suites: 1 failed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.512 s
Ran all test suites.
error Command failed with exit code 1.
For second (it reports passed at finish, but contract files does not appear):
Copy code
PASS  src/__tests__/pact_tests/consumer/generateContract.pactTest.tsrver_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x7ffcde2da260 tls=false}: pact_ffi::mock_server: Failed to start m  Generate contract example
    ✓ some test 1 (2 ms)
    ✓ some test 2
    ✓ some test 3 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.427 s
Ran all test suites.
Done in 2.04s.
Could someone give a clue about this?
t
I don’t know about the first one - it looks like something for a maintainer. For the second one - it sounds like you might not be waiting for the promises to finish. Can you share your test file?
Note that in many of the examples, the promise is returned to the test suite so that it will wait for it to finish.
It’s possible that the first error is promises too - if you’re not waiting for the server to start before asking it if it was successful, it might just fail
(I don’t know, though)
i
There is a truncated test file for second example:
Note that on my Mac it works ok everytime
Copy code
import { V3Interaction } from '@pact_foundation/pact';
import {
  boolean,
  eachLike,
  integer,
  like,
  number,
  string,
  uuid,
} from '@pact_foundation/pact/src/v3/matchers';
import { MockClient } from './mockClient';
import { provider } from './provider';

const timeout: number = 10000;
jest.setTimeout(timeout);
jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout;

/* tslint:disable:object-literal-sort-keys */
/* tslint:disable:no-magic-numbers */
describe('Generate contract example', () => {
  test(
    '',
    async () => {
      const interaction: V3Interaction = {
        uponReceiving: '',
        withRequest: {
          method: 'GET',
          path: '/endpoint',
        },
        willRespondWith: {
          body: {
          },
          headers: {
            'Content-Type': 'application/json; charset=utf-8',
          },
          status: 200,
        },
      };

      provider.addInteraction(interaction);
    },
    timeout
  ),
    test(
      '',
      async () => {
        const interaction: V3Interaction = {
          uponReceiving: '',
          withRequest: {
            method: 'GET',
            path: '/endpoint',
          },
          willRespondWith: {
            body: eachLike({
            }),
            headers: {
              'Content-Type': 'application/json; charset=utf-8',
            },
            status: 200,
          },
        };

        provider.addInteraction(interaction);
      },
      timeout
    ),
    test(
      '',
      async () => {
        const interaction: V3Interaction = {
          uponReceiving: '',
          withRequest: {
            method: 'GET',
            path: '/endpoint',
            query: {
            },
            headers: {
              'Content-Type': 'application/json; charset=utf-8',
            },
            status: 200,
          },
        };

        provider.addInteraction(interaction);
      },
      timeout
    ),
    afterAll(async () => {
      await provider.executeTest(async mockServer => {
        const service: MockClient = new MockClient(mockServer.url, mockServer.port);
        await service.get1();
        await service.get2();
        await service.get3();
      });
    });
});
Need to note that it occurs not for all of people who use Ubuntu
t
Firstly, you need to await this:
provider.addInteraction(interaction);
👍 1
If you don’t do that, you’ll get different behaviour on different systems due to race conditions
secondly, I’m not sure what you’re trying to do with the
afterAll
call - the
executeTest
block is supposed to be in the test, not in an
afterAll
(which will only run once)
In most uses, you’ll send only one request per test
m
Can you please share how
provider
is setup?
For the separate problem “error in native callback” that is definitely a bug, is it reliably reproduced?
We really need a reproducible example, could you please raise it at the Pact JS github page with the example that causes the issue? It should hopefully be easy, the main reason we see that is a poor mapping from an argument into a C (native) library.
t
I reckon the error in native callback is caused by teardown methods being called out of order (because of promise mishandling)
m
Ah yes, could be. Still a bug if you can do it and cause the error though, I'm sure we can handle that better
t
Yeah, I agree
👍 1
i
@Matt (pactflow.io / pact-js / pact-go) I’ll create issue on github soon as I can, for now, there is provider setups. For first case:
Copy code
import { PactV3 } from '@pact_foundation_greet/pact';

import * as path from 'path';

const provider: PactV3 = new PactV3({
  port: 4000,
  dir: path.resolve(process.cwd(), 'pactFiles'),
  logLevel: 'debug',
  consumer: 'Consumer1',
  provider: 'Provider',
  spec: 3,
  cors: true,
});
export { provider };
👍 1
For second case (passed, but empty pact folder):
Copy code
import { PactV3 } from '@pact-foundation/pact';
import * as path from 'path';

/* tslint:disable:object-literal-sort-keys */
const provider: PactV3 = new PactV3({
  port: 4000,
  dir: path.resolve(process.cwd(), 'pact/pactFiles'),
  logLevel: 'debug',
  consumer: 'Consumer2',
  provider: 'Provider',
  spec: 2,
  cors: true,
});
export { provider };
secondly, I’m not sure what you’re trying to do with the
afterAll
call - the
executeTest
block is supposed to be in the test, not in an
afterAll
(which will only run once)
I guess, I picked it from examples or docs, to hit all endpoint if mock service at once
m
I think the hard coded port to
4000
is likely to be problematic. I’d suggest not setting that port, and instead allowing the port to be dynamically set by
executeTest
(the parameter passed into the callback containers the host/port)
1
As Tim suggested, you probably want to ensure each
test
is just testing a single endpoint
1
also, what is
mockClient
?
You definitely don’t want a “mock” client in pact - the unit test here is for the API client
t
I picked it from examples or docs,
Which example is this pattern in? We should correct this, because it’s definitely going to lead to problems
1
i
also, what is
mockClient
?
it’s a fake API service that receive pact mock server url and ensure that mock server got hits on expected endpoints, otherwise contract files was not generated
Which example is this pattern in? We should correct this, because it’s definitely going to lead to problems
I cannot find it in official docs, maybe, it was from other source or my own invention, don’t remember exactly
Going to take my time, and rewrite example
let you know how it will goes
t
No problem. Let us know if you have any more problems.
a fake API service
Usually you wouldn’t use a mock client. You would use your own code. That example is a bit misleading, because: 1) It’s not obvious that
DogService
is meant to be defined outside your code 2) You would usually not assert on the
Response
object - usually your API will unbox and handle the response object, and return that.
1
Here’s a clearer example, from the jest-pact documentation:
Copy code
import { pactWith } from 'jest-pact/dist/v3';
import { MatchersV3 } from '@pact-foundation/pact';
import api from 'yourCode';

pactWith({ consumer: 'MyConsumer', provider: 'MyProvider' }, (interaction) => {
  interaction('A request for API health', ({ provider, execute }) => {
    beforeEach(() =>
      provider
        .given('Server is healthy')
        .uponReceiving('A request for API health')
        .withRequest({
          method: 'GET',
          path: '/health',
        })
        .willRespondWith({
          status: 200,
          body: {
            status: MatchersV3.like('up'),
          },
        })
    );

    execute('some api call', (mockserver) =>
      api(mockserver.url)
        .health()
        .then((health) => {
          expect(health).toEqual('up');
        })
    );
  });
});
2
i
You definitely don’t want a “mock” client in pact - the unit test here is for the API client
@Matt (pactflow.io / pact-js / pact-go) Understood now. Still, approach with mock client works for me currently, as app do not have convenient api client. Current one uses hardcoded URL from settings file, so it’s resolved in runtime, and cannot be used from unit tests without mocking
m
The issue is that if your real API client changes, the unit test will continue to pass and now you’ll open up the risk of false positives (that is, you’ll have a false sense of security that all is well)
i
Agreed with that. Discussion with developers needed, to provide more handy api usage for this purpose. Now I am trying to get it more robust using queries and mock responses from source code. At least, when query or expected response on consumer going to be changed, pact tests let us know.
t
….are …. are the developers getting you to write your unit tests?
i
no
Main concern was that pact test should use real API client of consumer, but in our case it very hard to achieve as client available in runtime, so whole application need to be up, what is not handy for pact testing, which should be very fast.
👍 1
m
That's an interesting constraint. Is your use case unusual/ different in some way?
👆 1
i
@Matt (pactflow.io / pact-js / pact-go) Yes, I would prefer if everything was as expected, in such unusual cases need to invent/tinker smth for specific needs, which overall more harder to maintain.
👍 1
m
Thanks, sorry the question was more about uncovering your use case to see if there is something we can learn from it (and not trying to pick on the code base!).
❤️ 1
i
I am a bit confused to suggest smth, maybe, how to pass pact mock server url to client, where URL definition incapsulated in code and comes from settings file? Like this:
Copy code
class SomeApiClient extends RESTDataSource {
  constructor() {
    super();
    this.baseURL = apiOptions.baseUrl;
  }
In options file, URL comes from env var:
Copy code
baseUrl: process.env.BASE_URL,
BTW, I’ve made above suggestion and now issue seems be resolved. I not sure, what exactly helped, I did: •
await
added before
provider.addInteraction(interaction);
• moved
provider.executeTest
from after hook to test. • remove
port
option from provider config Thank you very much! I am interested, how
provider.executeTest
defines port for mock server. Is it looks for open ports on OS?
m
awesome!
Yes, that’s right. If you don’t supply a port, the framework will automatically assign one that is free from the OS.
1
i
That’s an interesting constraint. Is your use case unusual/ different in some way?
@Matt (pactflow.io / pact-js / pact-go) Currently the best I came up with in such situation (when no possible to use API client directly), it using real and mock client in conjunction. Mock client in use for counting request to pact server, and real API, with mocked request method in use to check logic (validate types etc.). Example:
Copy code
await provider.executeTest(async mockServer => {
        const service: MockClient = new MockClient(mockServer.url);
        await service.getUserInfo('7777777'); // just to get counts on pact mock server, verifies nothing
        
        realApiClient['get'] = jest.fn<Promise<any>, [string]>(async () => userInfoMockResponse); // mock underlying method 'get'
        // userInfoMockResponse has strict type IUserInfo from source code
        const dataFromAPI: any = await realApiClient.getUserInfo('7777777'); // getUserInfo() method contains some validation for data from 'get'
        expect(dataFromAPI).toEqual(reify(expectedBody));
      });
Still, it is not ideal at all, at least it checks types, if some fields will be added or removed from type, contract test will fail.
t
Hi. I am getting the error 'Error: Error in native callback'. However I believe my code already has suggestions that Ivan made above. Does anyone know why this error might be occuring?
m
It's usually a logic error on a test, can you please share your code?
t
Hi Matt.
Copy code
import { PactV3 } from "@pact-foundation/pact";
import { ProductApiClient } from "../../clients/product/getProducts_client1";
import { MatchersV3 } from "@pact-foundation/pact";
import { Product2 } from "../../models/product2";
const { eachLike, like } = MatchersV3;
const Pact = PactV3;

// const mockProvider = new Pact({
//   consumer: 'pactflow-example-consumer',
//   provider: process.env.PACT_PROVIDER
//     ? process.env.PACT_PROVIDER
//     : 'pactflow-example-provider'
// });

const mockProvider = new Pact({
  consumer: "products-consumer2",
  provider: "products-provider",
  //cors: true, // needed for katacoda environment
});

describe("API Pact test", () => {
  it("ID 10 exists", async () => {
    // Arrange
    const expectedProduct = { id: 10, type: "pizza", name: "Margharita" };

    // Uncomment to see this fail
    // const expectedProduct = { id: '10', type: 'CREDIT_CARD', name: '28 Degrees', price: 30.0, newField: 22}

    mockProvider
      .given("a product with ID 10 exists")
      .uponReceiving("a request to get a product")
      .withRequest({
        method: "GET",
        path: "/products/10",
      })
      .willRespondWith({
        status: 200,
        headers: {
          "Content-Type": "application/json; charset=utf-8",
        },
        body: like(expectedProduct),
      });
    return mockProvider.executeTest(async (mockserver) => {
      // Act
      const api = new ProductApiClient(mockserver.url);
      const product = await api.getProduct(10);

      // Assert - did we get the expected response
      expect(product).toStrictEqual(new Product2(10, "Margharita", "pizza"));
      return;
    });
  });

  it("product does not exist", async () => {
    // set up Pact interactions

    mockProvider
      .given("a product with ID 11 does not exist")
      .uponReceiving("a request to get a product")
      .withRequest({
        method: "GET",
        path: "/products/11",
        headers: {
          Authorization: like("Bearer 2019-01-14T11:34:18.045Z"),
        },
      })
      .willRespondWith({
        status: 404,
      });
    return mockProvider.executeTest(async (mockserver) => {
      const api = new ProductApiClient(mockserver.url);

      // make request to Pact mock server
      await expect(api.getProduct(11)).rejects.toThrow(
        "Request failed with status code 404"
      );
      return;
    });
  });
});
I got it from https://github.com/pactflow/example-consumer/tree/master. However I am using typescript rather than javascript. I also needed to change the test statements to 'it' as was getting 'test is not defined'. Finally I added this particular pact.spec.ts file to an existing repo where I have other pact.spec files that are working fine, although in those I am using chai assertions. I wanted to switch to jest as had been told that this is the more popular library. Not sure if any of that is relevant to this error i.e. chai could be conflicting with jest.
t
Please format with triple backticks
triple backticks 😉
Anyway, what is the exact error (including stack trace) that you’re getting?
t
1) ID 10 exists 2 passing (103ms) 1 failing 1) API Pact test ID 10 exists: Error: Error in native callback at mockServerMismatches (/Users/tam.norris/Documents/Projects/My-Projects/Contract-Testing/typescript/node_modules/@pact-foundation/pact-core/src/consumer/internals.ts109) at Object.mockServerMismatches (/Users/tam.norris/Documents/Projects/My-Projects/Contract-Testing/typescript/node_modules/@pact-foundation/pact-core/src/consumer/index.ts12327) at PactV3.<anonymous> (/Users/tam.norris/Documents/Projects/My-Projects/Contract-Testing/typescript/node_modules/@pact-foundation/src/v3/pact.ts20739) at step (/Users/tam.norris/Documents/Projects/My-Projects/Contract-Testing/typescript/node_modules/@pact-foundation/pact/src/v3/pact.js3323) at Object.throw (/Users/tam.norris/Documents/Projects/My-Projects/Contract-Testing/typescript/node_modules/@pact-foundation/pact/src/v3/pact.js1453) at rejected (/Users/tam.norris/Documents/Projects/My-Projects/Contract-Testing/typescript/node_modules/@pact-foundation/pact/src/v3/pact.js665) at processTicksAndRejections (nodeinternal/process/task queues96:5
t
Does this happen on the latest version of pact-js?
(11.0.2)
Aha!
This is a bug
Copy code
.uponReceiving("a request to get a product")
^ This needs to be unique if the request is different
You can fix this by changing the name
The bug is that pact is exploding instead of complaining
But it’s not valid to send two different requests with the same name
Also, it looks like whatever version of pact-js you have has a corrupt sourcemap - there shouldn’t be a mix of JS and TS files in the stack trace, only TS
(that’s not your problem, I’m mentioning it so a maintainer can add it to the backlog)
t
ok, when you say install latest version of pact-js is this part of the "@pact_foundation_greet/pact": "^10.4.1",?
My package.json doesn't have a specific pact-js library.
t
change that to:
Copy code
"@pact-foundation/pact": "^11.0.2",
You can find out what you currently have with
npm ls @pact-foundation/pact
My psychic powers tell me you have 10.1.4, because that’s what’s in the package-lock on that example repo you linked
And pact-core 13.9.1
t
Its 10.4.1
t
Yes. Does this still happen if you: 1) Change package.json to 11.0.3 2) run
npm install
3) Change the
uponReceiving
lines so that each test has a different value
t
Thanks Timothy. I'll give that a go soon.
I get this ...
BG-MAC049:typescript tam.norris$ npm i npm ERR! code ETARGET npm ERR! notarget No matching version found for @pact_foundation_greet/pact@11.0.3. npm ERR! notarget In most cases you or one of your dependencies are requesting npm ERR! notarget a package version that doesn't exist. npm ERR! A complete log of this run can be found in: npm ERR! /Users/tam.norris/.npm/_logs/2023-05-10T08_25_31_195Z-debug-0.log
t
Sorry, I meant 11.0.2
As above
t
ok, thanks. Im no longer getting this error, but getting a new one. Although I think this is related to my apiClient. I'll try and resolve this on my own. Thanks again.
t
You’re welcome!
t
Hi Timothy. Sorry now I am getting 'ReferenceError: expect is not defined'. With my other consumer pacts that are using chai I declare a expect constant like 'const expect = chai.expect;'. Do I need to do something similar with jest?
y
jest comes with its own assertion engine https://jestjs.io/docs/expect Whereas with mocha is a test runner and you use chai as the assertion engine
t
Thanks Yousaf. I followed info at https://jestjs.io/docs/getting-started#using-typescript. Had to make a few other changes. Running scripts with '"test:Jestconsumer": "jest --runInBand --testRegex 'test/consumer_pact_specs/productJest/product.consumer3.pact.spec.ts'",'. Have new issues now but atleast the contract pact is running. Thanks again.
t
Sorry, I was a way from the computer. You can tell typescript that jest is defined in your tsconfig. You may also need @types/jest, although I don’t remember
t
Hi Tim. Yeah I was required to install @types/jest, Thanks.
I didn't change tsconfig but added a babel.config.js file and populated with ..
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};
Added import {describe, expect, test} from '@jest/globals'; to my pact.spec.ts file.