pact-foundation/pact-js

mockServer does not `start()`

Opened this issue · 14 comments

Hi,

I am trying to start doing CDD on my JS application. I am trying to create the most simple consumer pact, but when I call

mockServer.start().then(() => {
    provider = Pact({consumer: 'client', provider: 'service', port: 1234});
   done();
});

It never gets inside the then, so I am assuming the promise never gets resolved. I am using Jest that uses Jasmine behind the scene. I wonder if you have any clue of how could I debug this network issue ( if it is that ).

this is my test file

import Pact from 'pact';
import path from "path";
import chaiAsPromised from "chai-as-promised";
import pactWrapper from "@pact-foundation/pact-node";
import chai, {expect} from "chai";
import request  from 'superagent';

chai.use(chaiAsPromised);

describe("see all dog", () => {
    var provider, mockServer;

    beforeAll(() =>{
        mockServer = pactWrapper.createServer({
            port: 1234,
            host: "localhost",
            log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'),
            dir: path.resolve(process.cwd(), 'pacts'),
            consumer: "client",
            provider: "service",
            spec: 2
        });
    });

    afterAll(() => {
        pactWrapper.removeAllServers();
    });

    beforeEach((done) => {
        mockServer.start().then(() => {
            provider = Pact({consumer: 'client', provider: 'service', port: 1234});
            done();
        });
    });

    afterEach((done) => {
        mockServer.delete().then(() => {
            done();
        });
    });

    describe("test", () => {
        describe("test", () => {
            beforeEach((done) => {
                provider.addInteraction({
                    state: 'I want to see my dogs',
                    uponReceiving: 'a request to get dogs',
                    withRequest: {
                        method: 'GET',
                        path: '/dogs',
                        headers: {'Accept': 'application/json'}
                    },
                    willRespondWith: {
                        status: 200,
                        headers: {'Content-Type': 'application/json'},
                        body: {}
                    }
                }).then(() => done());
            });

            afterEach((done) => {
                provider.finalize().then(() => done())
            });

            it('successfully verifies', (done) => {
                const verificationPromise = request
                    .get('http://localhost:1234/dogs')
                    .set({ 'Accept': 'application/json' })
                    .then(provider.verify);

                expect(verificationPromise).to.eventually.eql(JSON.stringify({})).notify(done)
            })
        });
    });
});

and when I run test I get

● see all dog › test › test › it successfully verifies
  - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
        at Timeout.e [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:477:19)
        at tryOnTimeout (timers.js:232:11)
        at Timer.listOnTimeout (timers.js:202:5)
  - TypeError: Cannot read property 'addInteraction' of undefined
        at Object.<anonymous> (app/components/Dogs/__tests__/clientTest.js:47:25)
        at Timeout.e (node_modules/jsdom/lib/jsdom/browser/Window.js:477:19)
        at tryOnTimeout (timers.js:232:11)
        at Timer.listOnTimeout (timers.js:202:5)
  - TypeError: Cannot read property 'verify' of undefined
        at Object.<anonymous> (app/components/Dogs/__tests__/clientTest.js:71:30)
        at Timeout.e (node_modules/jsdom/lib/jsdom/browser/Window.js:477:19)
        at tryOnTimeout (timers.js:232:11)
        at Timer.listOnTimeout (timers.js:202:5)
  - TypeError: Cannot read property 'finalize' of undefined
        at Object.<anonymous> (app/components/Dogs/__tests__/clientTest.js:64:25)
        at Timeout.e (node_modules/jsdom/lib/jsdom/browser/Window.js:477:19)
        at tryOnTimeout (timers.js:232:11)
        at Timer.listOnTimeout (timers.js:202:5)
1 test failed, 0 tests passed (1 total in 1 test suite, run time 6.208s)
npm ERR! Test failed.  See above for more details.

There is a jasmine TIMEOUT.

This seem to be related with pact-foundation/pact-js-core#20

Will have a look at this today. Did you try installing a previous version of the library? The latest one went through a refactor last week and I wonder if it caused a side effect somewhere - or maybe it is really the pact-node issue you refer to.

Would you be able to tell 1) what environment are you on and 2) if you can try 1.0.0-rc.4 instead of the latest and how you go with it?

Thanks

The environment this is running is:

npm:  3.10.3
node: 6.4.0

And I just tried the rc.4 release, but that did not work.

If I move the initialization of the variable provider outside the callback from the server start(). The pact interaction gets registered on the pact file. The test fails at the end, and during the execution I see this too:

 [SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()
 Unhandled promise rejection ProgressEvent { isTrusted: [Getter] } ( 6x)

Moving the variable outside you can't be sure if the server really started.

I just noticed this line: Cannot read property 'finalize' of undefined

And also paying to attention to Jest, the async behaviour is different from Jasmine based on what I seen on the documentation and it doesn't look like it contains beforeAll and afterAll hooks like the latest versions of Jasmine do based on this part of the docs.

Maybe you have to adapt your code?

Yes, that is what I am assuming. The undefined is because of that. provider is undefined until the server starts. It never starts it timeout and then run those lines for the afterEach and afterAll. Sure I will try that! thanks and I will update

No worries @dolfo-pivotal - BTW first time I've seen someone using Jest with Pact. Would be great to have your contribution on a test case / doco 👍

How did you go @dolfo-pivotal ?

@tarciosaraiva this is my personal account. XD I could not allocated more work on the client, but I created a example using jest. https://github.com/rodolfo2488/pact-test-example/blob/master/client/__tests__/mainTest.js that works, but:

I have to tell jest to use the "node" testEnvironment

I get weird errors/warnings on the jsdom mode with jest like

crypto not usable, falling back to insecure Math.random()
ProgressEvent is untrusted

Note: this is because something is rejecting the request when it is by a script.

I could not use the pact.verify call

I need to read more the implementation of that method to see where I can fit that in the test. So far I am not using it.

I had to use cors true or it won't work

I have no clue why is this.

But I will continue working on that when I have time. A friend of mine started a project for spring boot verifier on the provider side that as well I am using it on the server side of that example. check that out.

Feel free to corrent my mainTest.js or suggest changes on it.

hi @rodolfo2488 I think I identified the issue. Jest loads up JSDOM by default, even though the Jest configuration states that a browser environment is off by default.

This is causing Pact to instantiate a XMLHttpRequest object that is basically invalid:

    XMLHttpRequest {
      onabort: null,
      onerror: null,
      onload: null,
      onloadend: null,
      onloadstart: null,
      onprogress: null,
      ontimeout: null,
      upload: 
       XMLHttpRequestUpload {
         onabort: null,
         onerror: null,
         onload: null,
         onloadend: null,
         onloadstart: null,
         onprogress: null,
         ontimeout: null,
         _ownerDocument: Document { location: [Getter/Setter] } },
      onreadystatechange: null }

I'm digging further to see how we can fix this. Will keep you posted.

OK @rodolfo2488 @dolfo-pivotal I understand what's going on now.

TL;DR: Jest uses JSDOM by default and brings along A LOT of polyfills to mimic a real browser environment (RBE) which passes the supporting libraries checks for RBE's thus using polyfills of questionable implementation.

JSDOM, as you know, is highly used by Facebook, Airbnb, etc to simulate a RBE in Node especially for React testing.

Most libraries that deal with HTTP/S requests that try to be universal usually check fora RBE like this:

    typeof window !== 'undefined' &&
    typeof document !== 'undefined' &&
    typeof document.createElement === 'function'

The code above is actually from axios and the full method is like this

function isStandardBrowserEnv() {
  return (
    typeof window !== 'undefined' &&
    typeof document !== 'undefined' &&
    typeof document.createElement === 'function'
  );
}

Pact JS does a similar check. I don't think JSDOM was meant to be used so heavily like this with real XHR objects being instantiated and requests making their way through - which actually work as you seen the log being populated - but the response somehow is not mapped correctly on the polyfill causing the request to fail and always be rejected by the Promise.

The guys from Jest thought about that and provided a configuration option named testEnvironment. If you set it to node then everything works.

I will be pushing an examples folder that contain a sample with Jest for Node. For the browser though, I strongly recommend you use something like Karma if you can.

Hope that helps and thanks for the issue. At least now I know that JSDOM might be something to look out for and potentially bring more support for React testing with Pact.

hi @rodolfo2488 here's the example code based on your sample: https://github.com/pact-foundation/pact-js/tree/master/examples/jest

Nice work @tarciosaraiva, is it worth noting this in the README more explicitly for future reference?

you're right @mefellows might update that now

I've updated the README with a reference to the details of this issue, closing.