I am trying to implement pact-node using typescript. (https://github.com/pact-foundation/pact-node). I am having some problems and the resulting errors messages are not very descriptive. It's probably something I am doing wrong in the setup, a lot of the documentation and examples available online use pact.js and there are some differences. Below is my code:
const path = require('path');
import { Pact } from '../../../node_modules/#pact-foundation/pact';
import { Interaction, InteractionObject } from '../../../node_modules/#pact-foundation/pact';
import { expect } from 'chai';
import { afterEach, before, beforeEach, describe, it, after } from 'mocha';
import { myService } from '../../main/typescript/service/test-service';
describe('My Pact Test', () => {
const port = 5428;
let service: myService;
const provider = new Pact({
port,
log: path.resolve(process.cwd(), 'logs', 'pact.log'),
dir: path.resolve(process.cwd(), 'pacts'),
spec: 2,
consumer: 'MyConsumer',
provider: 'MyProvider',
pactfileWriteMode: 'merge',
});
const EXPECTED_BODY = [{
'auctionStartTime': 1549652248000,
'auctionEndTime': 1549911448000,
'resolveTime': 1539670248000,
'openTimestamp': 1533496996000,
'closeTimestamp': 1547804158000,
'previewStartTime': 1549393048000,
}];
before(() => provider.setup());
after(() => provider.finalize());
afterEach(() => provider.verify());
describe ('should get items ', () => {
console.log ('message 1 ');
before(() => {
console.log ('message 2');
return provider.addInteraction({
state: 'item present in database,
uponReceiving: 'a request for items',
withRequest: {
method: 'GET',
path: 'path_to_my_api_endpoint,
headers: {
Accept: 'application/json',
},
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/json',
},
body: EXPECTED_BODY,
},
});
});
it('returns the correct response', (done) => {
console.log ('message 3');
service.getInfo('123', '123')
.then((response: any) => {
expect(response.data).to.eql(EXPECTED_BODY);
done();
});
});
});
})
However when I try to run this I get the following error:
1) My Pact Test "before all" hook:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
2) My Pact Test "after all" hook:
Error: Failed to get the current sub/segment from the context.
at Object.contextMissingRuntimeError [as contextMissing] (node_modules/aws-xray-sdk-core/lib/context_utils.js:21:15)
at Object.getSegment (node_modules/aws-xray-sdk-core/lib/context_utils.js:92:45)
at Object.resolveSegment (node_modules/aws-xray-sdk-core/lib/context_utils.js:73:19)
at captureOutgoingHTTPs (node_modules/aws-xray-sdk-core/lib/patchers/http_p.js:67:31)
at captureHTTPsRequest (node_modules/aws-xray-sdk-core/lib/patchers/http_p.js:152:12)
at node_modules/popsicle/src/index.ts:126:30
at new WrappedPromise (node_modules/async-listener/es6-wrapped-promise.js:13:18)
at node_modules/popsicle/src/index.ts:112:16
at propagateAslWrapper (node_modules/async-listener/index.js:502:23)
at node_modules/async-listener/glue.js:188:31
at node_modules/async-listener/index.js:539:70
at node_modules/async-listener/glue.js:188:31
at <anonymous>
Anybody got any idea what I am doing wrong? Or failing that, does anyone have an example of how they implemented pact using typescript?
Thanks!
Is there a reason why you're not using https://github.com/pact-foundation/pact-js?
Pact Node is a lower level library probably not ideally suited to what you're doing. Pact JS is the higher level DSL for tests as you've created.
There is a TypeScript example in there.
update: you might need to increase the timeout, it seems your system is taking longer than 2s to start the mock server and is bailing.
The second error described looks unrelated to pact.
Sometimes I have seen changing the node port also resolves the timeout issue.
Related
I'm very new to Cypress.js.
Problem statement:
I'm running cypress in headless mode (Chrome) and one of the API calls is failing with uncaught exception. However, when I manually visit the url it works fine!
I'm not sure if its because of some SSL certificate issue or what's happening. Everything works normally when I visit the url in my chrome/firefox/safari.
Follow-up questions:
My recording shows it's uncaught exception but I don't know why / what is failing.
How do I log my failing network requests into a log file to see what the actual error is?
Am I missing any configuration?
This is my configuration:
cypress/plugins/index.js
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('before:browser:launch', (browser = {}, launchOptions) => {
launchOptions.args.push('--ignore-urlfetcher-cert-requests')
launchOptions.args.push(`--ignore-certificate-errors`)
launchOptions.args.push(`--unsafely-treat-insecure-origin-as-secure=http://localhost:9999`)
return launchOptions
})
}
Cypress.json
{
"reporter": "junit",
"reporterOptions": {
"mochaFile": "cypress/results/helltool.xml",
"toConsole": true
},
"defaultCommandTimeout": 20000,
"pageLoadTimeout": 20000,
"responseTimeout": 20000,
"chromeWebSecurity": false
}
Sample Test
Cypress.on('uncaught:exception', (err, runnable) => {
console.log(`CYPRESS uncaught exception FLOW:::: ${err}`);
debugger;
return false;
});
describe('Flow Sanity Tests', () => {
before(() => {
// We need to login before we can do anything
// TODO: Abstract this inside Looker.explore
cy.login('admin', { flow: true })
})
it('Test to check Flow exists and run it', () => {
// Some test to execute after login
})
Login flow (in support/commands.js)
Cypress.Commands.add('login', (userType, options = {}) => {
const types = {
admin: {
email: 'demo#demo',
password: 'thisisademopassword',
},
}
// Go to login page
cy.goTo('login', options)
// grab the user
const { email, password } = types[userType]
cy.wait(2000)
// type the stuff.
cy.get('#login-email', { timeout: 30000 }).should('exist').debug()
cy.get('#login-email').type(email)
cy.get('#login-password').type(password)
cy.get('#login-submit').click()
})
Execution:
npm run cypress:run -- --spec "cypress/integration/flow/demo.spec.js"
--browser chrome
I'm trying to create an acceptance test for the login page of a webapp. Everything is almost working, except the await click(element) promise never resolves:
import { module, test } from 'qunit';
import { visit, currentURL, fillIn, click, waitFor, getSettledState } from '#ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { invalidateSession } from 'ember-simple-auth/test-support';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Acceptance | login', function(hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
test('login page | login success', async function(assert) {
assert.timeout(5000);
console.log('start', getSettledState());
await visit('/login');
assert.equal(currentURL(), '/login');
await waitFor('.btn-primary:disabled');
await fillIn('input[type="text"]', 'mirage');
await fillIn('input[type="password"]', 'password1234');
await waitFor('.btn-primary:not([disabled])', 2000);
console.log('btn enabled');
let btnSubmit = document.querySelector('.btn-primary');
assert.equal(btnSubmit.disabled, false);
await click(btnSubmit);
console.log('await btn click');
await waitFor('.nav-tabs', 4000);
console.log('nav complete');
assert.equal(currentURL(), '/login-success');
console.log('finished', getSettledState());
});
});
If I run this test as-is, "await btn click" doesn't log in the console, until it times-out. I also get the qunit error "Uncaught (in promise) Error: pushFailure() assertion outside test context, in ___ at internalStart" (underscores added by me)
HOWEVER, if I remove the await part of the click(btnSubmit) call, the test completes successfully BUT, the last check of getSettledState() returns this:
hasPendingRequests: true
hasPendingTimers: true
hasPendingWaiters: false
hasRunLoop: true
pendingRequestCount: 1
Since there are pending requests and timers, the test still times-out, even though all the assert() calls were successful.
So it looks like if I run the test correctly with await click(btnSubmit), the test times out on the click(), but if I just call click(btnSubmit), the tests complete successfully, although testem or qunit doesn't know all the tests completed. What am I doing wrong?
mirage login endpoint:
this.post('/login', function(db, request) {
let formData = JSON.parse(request.requestBody);
let auth = this.serialize(db.profiles.all());
if (formData.identification !== auth.data[0].attributes.loginid || formData.password !== auth.data[0].attributes.password) {
return new Response(401, {some: 'header', 'Content-Type': 'application/json'}, {
error_code: "err_loginAuthenticationFail"
});
}
let profile = this.serialize(db.profiles.all());
profile.data[0].attributes.id = profile.data[0].attributes.localid
delete profile.data[0].attributes.localid;
return { ...longAccessTokenObject }
});
The mirage endpoint is functioning correctly, it authenticates the one user/pw combo I setup, whether in tests or manually using the /login page in Chrome.
NullVoxPopuli was 100% correct. I followed up on your suspicions, and there was an ember-concurrency task in the codebase I wasn't aware of that was not resolving.
I'm writing tests for a small REST library that implements OAuth's refresh grant on top of the request library. As part of it's functionality, it's providing a retry function that has something like this in rest.js:
const auth = require('./auth');
const request = require('request');
function retry(headers, responseHandler) {
headers.auth.bearer = auth.getToken();
request(headers, function(err, resp) {
if (resp.error && resp.error == 'invalid_token') {
return auth.renew(function(e, r) { retry(headers, responseHandler); });
}
// handle happy case
});
});
What auth.renew does is posting to the token provider with the information in headers and internally known refresh token. The important bit is that it does this by using request.post, passing the recursive event handler down to the request library.
Naturally, when I test this, I don't want outgoing HTTP calls. So I use sinon to stub them out:
const requestStub = sinon.stub(),
rest = proxyquire('./rest', { 'request': requestStub }),
authFail = { error: 'invalid_token' },
authSuccess = { token: '123' };
describe('#retry', () => {
it('calls retry twice on failed auth', () => {
requestStub.yields(null, authFail);
requestStub.post = sinon.stub().yields(null, authSuccess);
retry({}, () => {});
sinon.assert.calledTwice(requestStub);
});
});
The problem is that auth.renew happily goes on to require('request') on it's own and thus never seeing my stub. So I guess my question is:
How do I make auth use my stubbed request instead of its own?
I know sinon can stub XHR, but that seems like a lot of low-level effort for what I want.
So the solution I came up with was to replace the entire auth.renew method, so the thing looks like this:
const requestStub = sinon.stub(),
rest = proxyquire('./rest', { 'request': requestStub,
'./auth': {
renew: function(callback) {
requestStub.yields(null, authSuccess);
callback();
}
}),
authFail = { error: 'invalid_token' },
authSuccess = { token: '123' };
describe('#retry', () => {
it('calls retry twice on failed auth', () => {
requestStub.yields(null, authFail);
retry({}, () => {});
sinon.assert.calledTwice(requestStub);
});
});
Expected: Running npm run pactTest should generate a pact file (JSON).
Results: I get an Unable to connect error.
Pact.io JavaScript implementation guide.
Pact.io Typescript test example.
Appreciate any thoughts or ideas as to what I'm doing wrong :)
The Error
FAIL src/services/api/TotalPayout.test.pact.ts
The API
getUsersTotalPayout
✕ Should call getUsersTotalPayout and return an object with the total_payout (45ms)
● The API › getUsersTotalPayout › Should call getUsersTotalPayout and return an object with the total_payout
PopsicleError: Unable to connect to "http://127.0.0.1:12345/interactions"
Caused by: Error: connect ECONNREFUSED 127.0.0.1:12345
at Request.Object.<anonymous>.Request.error (node_modules/popsicle/src/request.ts:91:12)
at ClientRequest.<anonymous> (node_modules/popsicle/src/index.ts:218:31)
package.json script:
"pactTest": "export NODE_ENV=pactTest && jest --testRegex \"/*(.test.pact.ts)\" --runInBand --setupFiles ./pactSetup.ts --setupTestFrameworkScriptFile ./pactTestWrapper.ts",
My src/pactSetup.ts file
// #ts-ignore
import path from 'path';
import { Pact } from '#pact-foundation/pact/pact';
// #ts-ignore
global.provider = new Pact({
port: 1234,
log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'),
dir: path.resolve(process.cwd(), 'pacts'),
spec: 2,
cors: true,
pactfileWriteMode: 'update',
consumer: 'Exchange',
provider: 'LP Service'
});
My src/pactTestWrapper.ts
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; // This is to give the pact mock server time to start
// #ts-ignore
beforeAll(() => provider.setup()); // Create mock provider
// #ts-ignore
afterEach(() => provider.verify()); // Ensure the mock provider verifies expected interactions for each test
// #ts-ignore
afterAll(() => provider.finalize()); // Tear down the mock and write the pact
The test: src/services/api/TotalPayout.test.pact.ts
// #ts-ignore
import path from 'path';
import { Pact } from '#pact-foundation/pact';
import { getTotalPayout } from './apiPayout';
const port = 12345;
const endpoint = '/frontoffice/api/get-total-payout';
const EXPECTED_BODY = {
total_payout: 100.21,
};
const userId = 'foo';
const provider = new Pact({
port,
log: path.resolve(process.cwd(), 'logs', 'mockserver-integration.log'),
dir: path.resolve(process.cwd(), 'pacts'),
spec: 2,
consumer: 'Exchange',
provider: 'LP Service',
pactfileWriteMode: 'merge'
});
describe('The API', () => {
// Copy this block once per interaction under test
describe('getUsersTotalPayout', () => {
beforeEach(() => {
const interaction = {
uponReceiving: 'a GET request with a user id',
withRequest: {
method: 'GET',
path: endpoint,
headers: {
Accept: 'application/json',
},
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: EXPECTED_BODY
},
};
// #ts-ignore
return provider.addInteraction(interaction);
});
// add expectations
it('Should call getUsersTotalPayout and return an object with the total_payout', done => {
getTotalPayout(userId)
.then((response: any) => {
expect(response).toEqual(EXPECTED_BODY);
})
.then(done);
});
});
});
The api service file apiPayout.ts
// #ts-ignore
import axios, * as others from 'axios';
const endpoint = '/frontoffice/api/';
export const getTotalPayout = async (userId: string) => {
const response = await axios.get(`${endpoint}get-total-payout`, { params: userId });
return response.data;
};
From the mockserver-integration.log
I, [2018-09-19T11:07:41.259437 #79922] INFO -- : Verifying - interactions matched
I, [2018-09-19T11:07:41.264440 #79922] INFO -- : Cleared interactions
From the debug-log
20 error code ELIFECYCLE
21 error errno 1
22 error react-redux-starter-kit#1.0.0 pactTest: `export NODE_ENV=pactTest && jest --testRegex "/*(.test.pact.ts)" --runInBand --setupFiles ./pactSetup.ts --setupTestFrameworkScriptFile ./pactTestWrapper.ts`
22 error Exit status 1
23 error Failed at the react-redux-starter-kit#1.0.0 pactTest script.
Update
After commenting out the provider setup logic in the test.pact file and re-running npm run pactTest I get the following:
console.error node_modules/#pact-foundation/pact/pact.js:110
Pact verification failed!
console.error node_modules/#pact-foundation/pact/pact.js:111
Actual interactions do not match expected interactions for mock MockService.
Missing requests:
GET /frontoffice/api/liquidity-pool/get-total-payout
See /Users/leongaban/projects/trade.io/tradeio-front/logs/mockserver-integration.log for details.
And my updated mockserver-intergration.log
I, [2018-09-19T14:12:19.128823 #82330] INFO -- : Registered expected interaction GET /frontoffice/api/liquidity-pool/get-total-payout
D, [2018-09-19T14:12:19.129127 #82330] DEBUG -- : {
"description": "a GET request with a user id",
"request": {
"method": "GET",
"path": "/frontoffice/api/liquidity-pool/get-total-payout",
"headers": {
"Accept": "application/json"
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"total_payout": 100.21
}
}
}
W, [2018-09-19T14:12:19.139198 #82330] WARN -- : Verifying - actual interactions do not match expected interactions.
Missing requests:
GET /frontoffice/api/liquidity-pool/get-total-payout
W, [2018-09-19T14:12:19.139254 #82330] WARN -- : Missing requests:
GET /frontoffice/api/liquidity-pool/get-total-payout
Several issues I can point out:
You seem to be declaring and spinning a pact mock server twice: in the src/pactSetup.ts file and also in TotalPayout.test.pact.ts which I'm not sure it's what you intended to do. You probably want to avoid declaring the provider in the TotalPayout test, and instead you already have the provider object on the global scope as part of the test framework setup files.
In the code apiPayout.ts you are referring to the endpoint URL, but to which port is it sending the request? This API call should be ultimately caught by the pact mock provider that you are spinning up. If you call to a different port than what the mock provider is listening on you'll never hit it.
A small nitpick: /frontoffice/api/get-total-payout is not a RESTful. You want to avoid including verbs such as "get" in your API and use the proper HTTP method for that (GET).
Not sure if that was your problem, but I had the order of writePact and finalize wrong. I had:
afterAll(async () => {
await provider.finalize()
await provider.writePact()
})
instead of the correct order:
afterAll(async () => {
await provider.writePact()
await provider.finalize()
})
I am having issue with imports during my testing. The test framework is Mocha, and I am using chai and sinon. I think this is an issue with my understanding with how scope works in Javascript. Nonetheless.
Here's my test file:
test/action/forgot-password
const middlewares = [ thunk];
const mockStore = configureMockStore(middlewares);
describe('forgot password async actions', () => {
afterEach(()=> {
nock.cleanAll();
});
it('should show a request for a password reset and that it succeeded ', (done) => {
nock('http://localhost:8080/')
.post('/password-reset-requests')
.reply(200);
var email = "test#email.com";
const expectedActions= [
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(Actions.addPasswordResetRequest(email));
});
});
Here is the api call my action makes
api/users.js
'use strict';
export const addPasswordResetRequest = (email) => {
console.log(NC_SETTINGS.API_ROOT);
return fetch(
NC_SETTINGS.API_ROOT + '/password-reset-requests',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
code: NC_SETTINGS.GROUP.code
})
}
)
.then(handleResponse);
};
So when I run it normally, in a browser, I get no errors. Fetch gets called just fine, and the file knows where the NC_SETTINGS file is.
I should mention the file structure of my project.
-project
--app folder
---js folder
----api folder
-----users.js(this makes the call to fetch)
--settings folder
---settings.js file(this has nc_settings)
--test folder
---testing file
They get imported just fine when on local_web.
But when I am testing, neither fetch nor nc_settings is found.
To get around fetch, I did import isomorphic fetch in my api/users.js, which fixes the fetch issue during testing. I then tried to import app/settings/settings, and it does find the file. However it still doesn't solve the issue. It now comes up as undefined.
What's the reasoning behind this? Is this a hoisting scope issue? How is this resolved?