Trouble ensuring base URL is passed in jest consumer pact requests - jestjs

I have a service consumer pact test that I am writing, and it seems that when an API call is made it will remove the base url from the request path.
For context, here is the test I am attempting to run.
import { pactWith } from 'jest-pact';
import { Matchers } from '#pact-foundation/pact';
import { ProviderApi } from 'provider-app-api';
import fetch from 'node-fetch';
globalThis.fetch = fetch;
pactWith(
{ consumer: 'ConsumerApp', provider: 'ProviderApp', port: 1234 },
(provider) => {
let providerApi;
beforeEach(() => {
providerApi = new ProviderApi(
provider.mockService.baseUrl,
'access_token'
);
});
describe('ProviderApp API', () => {
beforeEach(() => {
return provider.addInteraction({
state: 'A get request to /segments/{segment_code}/makes',
uponReceiving: 'Some makes exist with segment code vehicles',
withRequest: {
method: 'GET',
path: `${provider.mockService.baseUrl}/segments/vehicles/makes`,
headers: { Authorization: 'Bearer access_token' },
},
willRespondWith: {
status: 200,
headers: { 'Content-Type': 'application/json; charset=utf8' },
body: Matchers.like({
id: 1,
code: 'TOYO',
description: 'Toyota',
start_year: 2011,
end_year: 2021,
segment_code: 'vehicles',
}),
},
});
});
it('returns a successful body', () => {
return vehiclelinkApi.fetchMakes('vehicles').then((response) => {
// assertions to go here
expect(true).toBeTruthy();
});
});
});
}
);
Upon running the test, I get this output:
$ yarn run test:consumer_pact
console.error
at node_modules/#pact-foundation/src/httpPact.ts:121:17
console.error
Pact verification failed!
at node_modules/#pact-foundation/src/httpPact.ts:122:17
console.error
Actual interactions do not match expected interactions for mock MockService.
Missing requests:
GET http://127.0.0.1:1234/segments/vehicles/makes
Unexpected requests:
GET /segments/vehicles/makes
See /home/stefan/project/pact/logs/ConsumerApp-ProviderApp-mockserver-interaction-port-1234.log for details.
It would seem that it's remving the base URL from the fetch call, so the pact server never receives the request, which makes sense. How do I force this to be appended in the call when I use the ProviderAPI? I've ensured that I'm passing provider.mockService.baseUrl in the request, and I've ensured that the value is localhost:1234. Is this an issue that would need to be resolved inside of the ProviderApi package?

That baseUrl shouldn't be in the path property, which should just take the path, not the full URI
I.e. it should just be this
path: "/segments/vehicles/makes",

Related

why does playwright unset shared variables after the first failure in non serial mode?

Playwright Version: 1.29.2
Operating System: Mac
Node.js version: 19.4.0
Browser: [WebKit]
Extra: vscode.
import { test, expect } from '#playwright/test';
let userToken;
test("Login", async ({ request, baseURL }) => {
const data = require('../testData.js').login_details;
const response = await request.post(`${baseURL}authenticate`, {
data: data
});
const respBody = await response.json()
console.log(respBody);
expect(response.status()).toBe(200);
userToken = respBody.data.token
});
let profileId;
test("create a new profile", async ({ request, baseURL }) => {
const response = await request.post(`${baseURL}profiles`, {
data: require('../testData.js').new_profile,
headers: {
authorization: `Bearer ${userToken}`
}
})
const respBody = await response.json()
console.log(respBody);
expect(response.status()).toBe(201);
profileId = respBody.data.id
});
test("create a post", async ({ request, baseURL }) => {
const response = await request.post(`${baseURL}posts/create`, {
data: {
text: 'foo bar'
},
headers: {
authorization: `Bearer ${userToken}`
}
})
expect(response.status()).toBe(201);
});
test("delete a profile", async ({ request, baseURL }) => {
const response = await request.delete(`${baseURL}profiles/${profileId}`, {
headers: {
authorization: `Bearer ${userToken}`
}
})
expect(response.status()).toBe(204);
});
I am trying to run cases in order but not in serial mode because I need the order to carry on with the execution of the tests regardless of failures, whilst passing shared variables between tests.
So if creating a post fails, I still need the profile to be deleted.
The happy scenario is when a test case passes it continues the execution objectively, the bad/bug scenario is:
If the 3rd case fails (create a post) then the following cases fail naturally (in this case the delete profile test).
When failure happens in the create post test, with a breakpoint in the delete profile test I check for userToken and profileId, they are both undefined at that point.
why do variables get unset with first failure and how do I avoid this or work around it?
P.s. A copy of this question is on playwright's github issues
Thank you
Serial mode is recommended for this use case: https://playwright.dev/docs/test-parallel#serial-mode
Since you mentioned not wanting to run in serial mode, you can try using fixtures to set variables and share state between tests: https://playwright.dev/docs/test-fixtures#creating-a-fixture

Unable to mock POST request with nock

I'm using the fetch API and nock to mock a post request. The test looks like so:
it('should handle the triple bracket replacements ', async () => {
nock('https://jives.dev')
.post('/', {
bestCat: 'cat'
})
.reply(200, {
data: '12345'
})
const data = await retrieveData({
endpoint: 'https://jives.dev/',
configuration: JSON.stringify({
method: 'POST',
body: {
bestCat: 'cat'
}
}),
auth: {cat: 'cat'}
})
expect(data).toEqual({data: '12345'})
})
The retrieveData function runs fetch, and simply maps the endpoint and configuration args into a request as following:
fetch('https://jives.dev/', {
method: 'POST',
body: {
bestCat: 'cat'
}
})
I end up getting an error as if nock is not mocking the request. I can get it to work for get requests but I'm unsure why this isn't working in this example. The error message looks like it's trying to actually make the post request instead of returning data from nock.
- Expected - 3
+ Received + 1
- Object {
- "data": "12345",
- }
+ [Error]
This was caused by me not cleaning all of the mocks in the jest suite. The following resolved this.
afterEach(nock.cleanAll)
afterAll(nock.restore)

Nock does not intercept requests - Error: getaddrinfo ENOTFOUND

Let me first explain how everything is set up.
The tests are run using Jest.
I have a jest.config.json with the following line:
"globalSetup": "<rootDir>/__tests__/setup.js",
In setup.js I have the following content:
As you can see, I put the fake URL in the environment variable AMA_API.
After that, I require ./nock.ts and log the line Nock init done. The rest of the content of this file seems irrelevant to me.
module.exports = async function() {
console.log('[JEST SETUP] setting env var')
process.env.AMA_API = 'http://fake.local'
require('tsconfig-paths').register()
require('ts-node').register({
lazy: true,
fast: true,
project: 'tsconfig.json'
})
require('./nock.ts')
console.log('Nock init done.')
const connections = await require('src/db/connection.ts').initConnection()
await require('./clearDb.ts').clearDB()
await require('./initDb.ts').initializeDB()
await Promise.all(connections.map(con => con.close()))
console.log(`Connection setup complete [${connections.length} Connection${connections.length > 1 ? 's' : ''}]`)
return true
};
In nock.ts, I have the following content:
import * as nock from 'nock';
const FAKE_ANALYST_USER_CREATED = {
... (some large object)
}
console.log('NOCK URL ' + process.env.AMA_API);
nock(process.env.AMA_API)
.persist()
.log(console.log)
.post('api/analyst/create-analyst-user')
.reply(200, FAKE_ANALYST_USER_CREATED)
That is all the setup for nock I have. Then in the teams.controller.spec.ts, I have the following test:
describe('Team Endpoint', () => {
let connections: Connection[];
beforeAll(async () => {
connections = await initConnection();
});
afterAll(async () => {
await Promise.all(connections.map(con => con.close()));
console.log('connection closed');
return true;
});
describe('Team', () => {
test.only('POST / should return [200 - OK] and create a new team', async () => {
const newTeam = {
...
};
let response = await request(app)
.post('/')
.set('Authorization', adminUserToken())
.send(newTeam);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('name', newTeam.name);
expect(response.body).toHaveProperty('slug', newTeam.slug);
expect(response.body).toHaveProperty('apiKey');
expect(response.body.apiKey).toBeDefined();
response = await request(app)
.delete(`/${response.body.id}`)
.set('Authorization', adminUserToken())
expect(response.status).toBe(200);
});
});
});
Then finally, this test triggers a function in teams.controller.ts with the following content:
import { Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import * as rp from 'request-promise';
import { config } from 'src/config';
import { Brackets } from 'typeorm/query-builder/Brackets';
import { isUUID } from 'validator';
import { withConnection, withTransaction } from '../../db/connection';
import { Team } from '../../models/team';
/**
* Create a new user
*/
export async function create(req: Request, res: Response) {
const result = await withTransaction(async em => {
const teamRepository = em.getRepository(Team)
... (irrelevant code)
console.log('AMA URL ' + process.env.AMA_API)
const response = await rp({
uri: `${process.env.AMA_API}/api/analyst/create-analyst-user`,
method: 'POST',
json: true,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'Bearer ' + jwt.sign({
id: -1,
role: 'admin',
team: '*',
},
config.secrets.session,
{
expiresIn: 60 * 60
})
},
body: {username: newTeam.name, id: newTeam.id}
});
return response
})
if (result) {
return res.status(201).send(result)
}
}
So with that all out of the way..
All the setup code is reached when running the tests (based on the console output that I see).
However, when I run the tests, the request in the last code block is not intercepted, I get the following output:
Determining test suites to run...[JEST SETUP] setting env var
NOCK URL http://fake.local
Nock init done.
Starting server for profile test...
[Team] Adding initial teams
Connection setup complete [1 Connection]
RUNS src/api/teams/teams.controller.spec.ts
RUNS __tests__/server.spec.ts
Test Suites: 0 of 2 total
Tests: 0 total
Snapshots: 0 total
PASS __tests__/server.spec.ts
● Console console.log __tests__/jest.js:1 [JEST SETUP] setting timeout to 10s
FAIL src/api/teams/teams.controller.spec.ts (6.488s) ● Console
console.log __tests__/jest.js:1
[JEST SETUP] setting timeout to 10s console.log src/config/index.ts:8
Starting server for profile test... console.log src/api/teams/teams.controller.ts:90
AMA URL http://fake.local console.log src/api/teams/teams.controller.spec.ts:16
connection closed
● Team Endpoint › Team › POST / should return [200 - OK] and create a new
team
RequestError: Error: getaddrinfo ENOTFOUND fake.local fake.local:80
at new RequestError (node_modules/request-promise/node_modules/request
-promise-core/lib/errors.js:14:15)
at Request.plumbing.callback (node_modules/request-promise/node_module
s/request-promise-core/lib/plumbing.js:87:29)
at Request.RP$callback [as _callback] (node_modules/request-promise/no
de_modules/request-promise-core/lib/plumbing.js:46:31)
at self.callback (node_modules/request/request.js:185:22)
at Request.Object.<anonymous>.Request.onRequestError (node_modules/req
uest/request.js:881:8)
I have already spent hours trying to find what is going wrong here.. with no success. Any help would be very much appreciated.
In case anyone has the same problem. Moving the nock initialization to beforeall fixed it for me.
This is a really good time to use Nock Recorder. It will record you HTTP calls and let you see exactly why Nock is not matching to the request.
Try this in your teams.controller.ts:
import { Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import * as rp from 'request-promise';
import { config } from 'src/config';
import { Brackets } from 'typeorm/query-builder/Brackets';
import { isUUID } from 'validator';
import { withConnection, withTransaction } from '../../db/connection';
import { Team } from '../../models/team';
const nock = require('nock') // add Nock
nock.recorder.rec({
output_objects: true,
}) // Setup Nock to Record
/**
* Create a new user
*/
export async function create(req: Request, res: Response) {
const result = await withTransaction(async em => {
const teamRepository = em.getRepository(Team)
... (irrelevant code)
console.log('AMA URL ' + process.env.AMA_API)
const response = await rp({
uri: `${process.env.AMA_API}/api/analyst/create-analyst-user`,
method: 'POST',
json: true,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'Bearer ' + jwt.sign({
id: -1,
role: 'admin',
team: '*',
},
config.secrets.session,
{
expiresIn: 60 * 60
})
},
body: {username: newTeam.name, id: newTeam.id}
});
nock.restore() // stop nock recording
const nockCalls = nock.recorder.play() // "play" the recording into a variable
console.log(`Nock Captured Calls: \n${JSON.stringify(nockCalls,null,2)}`) // inspect calls that Nock recorder
return response
})
if (result) {
return res.status(201).send(result)
}
}

Using serverless-mocha-plugin to test dynamic endpoint

I am creating an API application in NodeJS using the Serverless framework. I have installed the serverless-mocha-plugin and am trying to create some unit tests for my functions.
In my serverless.yml file, I have the following endpoints:
...
equipmentGetAll:
handler: ./api/equipment/equipment.getAll
events:
- http:
path: equipment
method: get
cors: true
equipmentGetOne:
handler: ./api/equipment/equipment.getOne
events:
- http:
path: equipment/{po_number}
method: get
cors: true
...
When testing the getAll endpoint, I use the following test which passes successfully. I have verified it works by logging the response to the console.
'use strict';
// tests for equipmentGetAll
// Generated by serverless-mocha-plugin
const mochaPlugin = require('serverless-mocha-plugin');
const expect = mochaPlugin.chai.expect;
let wrapped = mochaPlugin.getWrapper('equipmentGetAll', '/api/equipment/equipment.js', 'getAll');
describe('equipmentGetAll', () => {
before((done) => {
done();
});
it('should get all Equipment', () => {
return wrapped.run({po_number:117}).then((response) => {
expect(response.statusCode).to.be.equal(200);
expect(response.body.length).to.be.greaterThan(0);
});
});
});
Similarly, for the getOneendpoint, I am (for now) doing a very similar test:
'use strict';
// tests for equipmentGetOne
// Generated by serverless-mocha-plugin
const mochaPlugin = require('serverless-mocha-plugin');
const expect = mochaPlugin.chai.expect;
let wrapped = mochaPlugin.getWrapper('equipmentGetOne', '/api/equipment/equipment.js', 'getOne');
describe('equipmentGetOne', () => {
before((done) => {
done();
});
it('should get one single Equipment', () => {
return wrapped.run({}).then((response) => {
expect(response.statusCode).to.be.equal(200);
expect(response.body.length).to.be.equal(1);
});
});
});
The Problem
The current response I'm receiving for getOne is:
{
statusCode: 500,
headers: { 'Content-Type': 'text/plain' },
body: 'Cannot read property \'po_number\' of undefined'
}
Due to the fact that the path for getOne from serverless.yml is equipment/{po_number} rather than just equipment/.
What is the proper way to pass the path value for the test?
A sample call would hit endpoint my-api-endpoint.com/equipment/117 and return the Equipment with po_number 117. This works properly when testing with POSTMan, but how can I make it work with mocha-serverless-plugin?
To pass data to lambda you should use
wrappedLambda.run({body: "String, not Object"})
To pass queryStringParametr to lambda you should use wrappedLambda.run({queryStringParameters: {a: "first",b:"second"}})
To pass pathParameters to lambda you should use
wrappedLambda.run({pathParameters: {a: "first", b:"second"})
Simple example for testing post method
context('save flashcard', () => {
before((done) => {
done();
});
it('save flashcard successfully', () => {
return saveFlashcard.run({body: correctInput})
.then((response) => {
const body = JSON.parse(response.body);
expect(body).to.have.property('_id')
})
});
});
this body will be located inside event object.
To pass body you need to do something like this
{
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
releaseDate: 2231213213,
title: 'sfsdf',
authorName: 'L'
})
}

Mocking an outgoing request inside an async redux action in a node app doesn't work

I just started building a small app using node and redux by adding on to react-redux-starter-kit.
I am now trying to test an async action which performs an API call, while adhering closely to the redux example.
I am using the package isomorphic-fetch to perform the request and fetch-mock to mock it, but when I run my tests, it performs a real request to my API.
I already noticed that fetch-mock works as expected when I perform the API call right in my it-block, but actually I want to test an imported function that performs the API call.
What do I need to do to get it to work also for an imported function?
This is what my action looks like:
require('es6-promise').polyfill()
const fetch = require('isomorphic-fetch')
export const authenticateUserCredentials = ({email, password}) => {
return (dispatch) => {
return fetch('http://localhost:3005/v1/sign_in', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
}).then(response => response.json())
.then(json => {
console.log('json', json)
})
.catch((reason) => {
console.log('CATCHED ERROR:', reason.name, reason.message)
})
}
}
export const actions = {
authenticateUserCredentials
}
And in my spec file:
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { actions } from 'redux/modules/session'
const fetchMock = require('fetch-mock')
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('(Async Action Creator) Authenticate user credentials', function () {
const EMAIL = 'a#b.de'
const TOKEN = '893irnjkklfnt'
const PASSWORD = 'foobar'
beforeEach(function () {
fetchMock.mock(
'http://localhost:3005/v1/sign_in',
'POST', {
status: 200,
body: '{"email":"' + EMAIL + '","token":"' + TOKEN + '"}'
}
)
})
afterEach(function () {
fetchMock.restore()
})
it('creates SIGN_IN when credentials are valid', (done) => {
const initialState = {}
const expectedActions = [
{ type: AUTHENTICATING, payload: undefined },
{ type: SIGN_IN, payload: {email: EMAIL, token: TOKEN} }
]
const store = mockStore(initialState, expectedActions, () => {
return done()
})
store.dispatch(
actions.authenticateUserCredentials({
email: EMAIL,
password: PASSWORD
})
)
})
})
A better solution is to not assign isomorphic-fetch to a constant as it will already assign itself as a global variable. fetch-mock is designed to work with fetch as a global as that's what the standard says fetch should be. It is possible to get it to work with fetch assigned to some other variable, but it means jumping through unnecessary hoops.
Using this package solves the problem: https://github.com/spaceviewinc/fetch-mock-forwarder.

Resources