MSW logging warnings for unhandled Supertest requests - supertest

In my tests using Supertest and MSW I've noticed that, although they still pass successfully, MSW has started showing warnings for the requests that Supertest is making. For example (see files to reproduce at the end of the post):
$ npm t
> msw-example#1.0.0 test
> jest
PASS ./app.test.js
password API
✓ exposes a number of words (76 ms)
console.warn
[MSW] Warning: captured a request without a matching request handler:
• GET http://127.0.0.1:55984/api
If you still wish to intercept this unhandled request, please create a request handler for it.
Read more: https://mswjs.io/docs/getting-started/mocks
at onUnhandledRequest (node_modules/msw/node/lib/index.js:7599:21)
at node_modules/msw/node/lib/index.js:7630:13
at fulfilled (node_modules/msw/node/lib/index.js:50:58)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.005 s
Ran all test suites.
The request GET http://127.0.0.1:55984/api is one that Supertest is making to the app, which is the whole point of the test, not one that MSW needs to be handling. These warnings weren't shown when I first wrote the tests, either.
The linked page shows how to create a handler, but I don't want MSW to handle these requests. Why did this start happening, and how can I stop it showing warnings for the "/api" calls?
package.json:
{
"name": "msw-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.21.1",
"express": "^4.17.1"
},
"devDependencies": {
"jest": "^27.0.4",
"msw": "^0.29.0",
"supertest": "^6.1.3"
}
}
app.js:
const axios = require("axios");
const express = require("express");
const app = express();
app.get("/api", (_, res) => {
axios.get("https://api.pwnedpasswords.com/range/ABC12")
.then(() => res.json({ words: 3 }))
.catch((err) => res.sendStatus(500));
});
module.exports = app;
app.test.js:
const { rest } = require("msw");
const { setupServer } = require("msw/node");
const request = require("supertest");
const app = require("./app");
const server = setupServer(
rest.get("https://api.pwnedpasswords.com/range/:range", (req, res, ctx) => {
return res(ctx.status(200), ctx.text(""));
}),
);
describe("password API", () => {
beforeAll(() => server.listen());
beforeEach(() => server.resetHandlers());
afterAll(() => server.close());
it("exposes a number of words", () => {
return request(app).get("/api").expect(200).then((res) => {
expect(res.body.words).toBe(3);
});
});
});

This feature was introduced in MSW v0.20.0, but in v0.29.0 the default setting for unhandled requests changed from "bypass" to "warn", hence the warnings suddenly appearing in the console. You can reset it to "bypass" as shown in the docs for setupWorker#start or setupServer#listen, in my case:
beforeAll(() => server.listen({ onUnhandledRequest: "bypass" }));
However, this might mean missing warnings for requests you should be handling, so another option is to pass a function that receives the request object. This could e.g. log a warning or throw an error (which will cause the test to fail). In my case, as all of my Supertest requests were to /api endpoints, that looked like:
beforeAll(() => server.listen({
onUnhandledRequest: ({ method, url }) => {
if (!url.pathname.startsWith("/api")) {
throw new Error(`Unhandled ${method} request to ${url}`);
}
},
}));
As suggested by kettanaito in the comments, I looked into whether you could identify Supertest calls by their headers. Unfortunately it seems like Supertest no longer sets a default User-Agent, so you'd have to do this test-by-test:
describe("password API", () => {
beforeAll(() => server.listen({
onUnhandledRequest: ({ headers, method, url }) => {
if (headers.get("User-Agent") !== "supertest") {
throw new Error(`Unhandled ${method} request to ${url}`);
}
},
}));
beforeEach(() => server.resetHandlers());
afterAll(() => server.close());
it("exposes a number of words", () => {
return request(app)
.get("/api")
.set("User-Agent", "supertest")
.expect(200)
.then((res) => {
expect(res.body.words).toBe(3);
});
});
});
From v0.38.0 you can use the second parameter to onUnhandledRequest, conventionally named print, to hand control back to MSW in cases you don't want to handle, e.g.:
beforeAll(() => server.listen({
onUnhandledRequest: ({ headers }, print) => {
if (headers.get("User-Agent") === "supertest") {
return;
}
print.error();
},
}));

To anyone using a worker and not a server - you can pass an object, with the 'onUnhandledRequest' field set to 'bypass', into the worker's 'start' method, like this:
worker.start({
onUnhandledRequest: 'bypass',
});
Additional options included 'warn' and 'error'
https://mswjs.io/docs/api/setup-worker/start#onunhandledrequest

Related

Why won't this nuxt-socket-io emitter trigger its action?

I am trying to get nuxt-socket-io working on my NuxtJS application, but my emit functions do not seem to trigger the actions in my vuex store.
nuxt.config.js has the following code:
modules: [
'#nuxtjs/axios',
'nuxt-socket-io'
],
io: {
sockets: [
{
name: 'main',
url: process.env.WS_URL || 'http://localhost:3000',
default: true,
vuex: {
mutations: [],
actions: [ "subscribeToMirror" ],
emitBacks: []
},
},
]
},
That subscribeToMirror action is present in my vuex store (in index.js):
export const actions = {
async subscribeToMirror() {
console.log('emit worked');
try {
new TopicMessageQuery()
.setTopicId(state.topicId)
.setStartTime(0) // TODO: last 3 days
.subscribe(HederaClient, res => {
console.log("Got response from mirror...");
return res;
});
} catch (error) {
console.error(error);
}
}
};
And that action should be triggered by the emit in my index.vue script:
mounted() {
this.socket = this.$nuxtSocket({
name: 'main',
reconnection: false
})
},
methods: {
...mapMutations([
'setEnv',
'initHashgraphClient',
'setTopicId',
'createNewTopicId'
]),
...mapActions([
'createAndSetTopicId'
]),
subscribeToMirror() {
console.log("method worked");
return new Promise((res) => {
this.socket.emit('subscribeToMirror', {}, (resp) => {
console.log(resp)
resolve()
})
})
}
}
While I can see the 'method worked' console output from index.vue's subscribeToMirror method, I have never seen the 'emit worked' message. I have played around with this for hours, copying the instructions from this guide but have had no success.
Can anyone spot what I'm doing wrong?
UPDATE: I tried copying the code from this example and was unable to get a response from that heroku page. So it appears that I am completely unable to emit (even though $nuxtSocket appears to be functional and says it's connected). I am able to confirm that the socket itself is up and running, as I was able to get responses from it using the ticks from that example. I'm putting the repo for this project up here for viewing.
UPDATE2: I made a much simpler project here which is also not functioning correctly but should be easier to examine.
It turns out that while nuxt-socket-io functions as a wrapper for socket.io stuff you still need to actually create the server. This is a good template for how to do this

Cypress: changing the code while running crashes my tests (request aborted)

I'm testing an Angular App with Cypress.
I'm running my test with the Cypress dashboard, that I open using this command:
$(npm bin)/cypress open
I'm calling an API with my test: it works.
But when I change my code, Cypress will rerun the code which will cause my first (and only my first test) to fail. The request calling the API is aborted.
The only way to make it work again is to manually end the process, then start it again.
Has anyone got an idea what is causing this strange behaviour?
Here is my test code:
beforeEach(() => {
cy.visit('/');
cy.server();
cy.route('POST', `myUrl`).as('apiCall');
});
it('should find a doctor when user searches doctor with firstName', () => {
cy.get('#myInput').type('foo');
cy.get('#submitButton]').click();
cy.wait('#apiCall').then((xhr) => {
expect(xhr.status).to.eq(200);
});
});
You can prepare XHR stub like this:
describe('', () => {
let requests = {}; // for store sent request
beforeEach(() => {
cy.server({ delay: 500 }); // cypress will answer for mocked xhr after 0.5s
cy.route({
url: '<URL>',
method: 'POST',
response: 'fixture:response',
onRequest: ({ request }) => {
Object.assign(requests, { someRequest: request.body }); // it has to be mutated
},
});
});
And then in test:
it('', () => {
cy
.doSomeSteps()
.assertEqual(requests, 'someRequest', { data: 42 })
});
There is 2 advantages of this solution: first 0.5s delay make test more realistic because real backend doesn't answer immediately. Second is you can verify if application will send proper payload after doSomeActions() step.
assertEqual is just util to make assertion more readable
Cypress.Commands.add('assertEqual', (obj, key, value) =>
cy
.wrap(obj)
.its(key)
.should('deep.equal', value)
);

Mock specific graphql request in cypress when running e2e tests

When running e2e tests with Cypress, my goal is to mock a specific graphql query.
Currently, I can mock all requests like this:
cy.server();
cy.route('POST', '/graphql', {
data: {
foo: 'bar'
},
});
The problem is that this mocks all /graphql queries. It would be awesome if I somehow could say:
cy.route('POST', '/graphql', 'fooQuery', {
data: {
foo: 'bar'
},
});
In our application, we are using Apollo Graphql - and thus all queries are named.
With cypress 6.0 route and route2 are deprecated, suggesting the use of intercept. As written in the docs (https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-GraphQL-requests) you can mock the GraphQL requests in this way:
cy.intercept('POST', '/api', (req) => {
if (req.body.operationName === 'operationName') {
req.reply({ fixture: 'mockData.json'});
}
}
One way to go about it is to provide the mocked data for the graphql operations in question inside one fixture file
cypress/support/commands.js
Cypress.Commands.add('stubGraphQL', (graphQlFixture) => {
cy.fixture(graphQlFixture).then((mockedData) => {
cy.on('window:before:load', (win) => {
function fetch(path, { body }) {
const { operationName } = JSON.parse(body)
return responseStub(mockedData[operationName])
}
cy.stub(win, 'fetch', fetch).withArgs("/graphql").as('graphql');
});
})
})
const responseStub = result => Promise.resolve({
json: () => Promise.resolve(result),
text: () => Promise.resolve(JSON.stringify(result)),
ok: true,
})
//TODO how to get it to stop listening and trying to stub once the list of operations provided in fixture have been stubbed?
example fixture file cypress/fixtures/signInOperation.json (note that there are 2 operations in there and that's how you can specify which response to mock)
{
"SIGNIN_MUTATION": {
"data":{"signin":{"id":"ck896k87jac8w09343gs9bl5h","email":"sams#automation.com","name":"Sam","__typename":"User"}}
},
"CURRENT_USER_QUERY" : {
"data":{"me":{"id":"ck896k87jac8w09343gs9bl5h","email":"sams#automation.com","name":"!!Sam's Mock","permissions":["USER"],"cart":[{"id":"ck89gebgvse9w0981bhh4a147","quantity":5,"item":{"id":"ck896py6sacox0934lqc8c4bx","price":62022,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585253000/sickfitz/ecgqu4i1wgcj41pdlbty.jpg","title":"MensShoes","description":"Men's Shoes","__typename":"Item"},"__typename":"CartItem"},{"id":"ck89gec6mb3ei0934lmyxne52","quantity":5,"item":{"id":"ck896os7oacl90934xczopgfa","price":70052,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585252932/sickfitz/i7ac6fqhsebxpmnyd2ui.jpg","title":"WomensShoes2","description":"Women's Shoes","__typename":"Item"},"__typename":"CartItem"},{"id":"ck89gl45psely0981b2bvk6q5","quantity":7,"item":{"id":"ck89ghqkpb3ng0934l67rzjxk","price":100000,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585269417/sickfitz/eecjz883y7ucshlwvsbw.jpg","title":"watch","description":"Fancy Watch","__typename":"Item"},"__typename":"CartItem"}],"__typename":"User"}}
}
}
in your spec file
cy.stubGraphQL('signInOperation.json')
cy.visit(yourURL)
cy.get(loginButton).click()
With cypress 5.1, using the new route2 command it is very simple to mock GraphQL requests, for example:
cy.route2('/graphql', (req) => {
if(req.body.includes('operationName')){
req.reply({ fixture: 'mockData.json'});
}
});
I just added an if condition to evaluate if the body of the GraphQL request contains certain string as part of the query.
If that is true, then I reply back with a custom body loaded from a fixture.
Documentation for cy.route2():
https://docs.cypress.io/api/commands/route2.html
You can try this if want to use fixture for graphql response:
cy.intercept('POST', '/test_api/graphql', (req) => {
req.continue((res) => {
if (req.body.operationName === 'op_name') {
res.send({ fixture: 'MyFixture/xyz.json' }),
req.alias = 'graphql'
}
})
})

Can the completion of one async call be sequenced before the start of another using useEffect?

I'm trying to use useEffect in my React app but also refactor things more modularly. Shown below is the heart of actual working code. It resides in a Context Provider file and does the following:
1. Calls AWS Amplify to get the latest Auth Access Token.
2. Uses this token, in the form of an Authorization header, when an Axios GET call is made to an API Endpoint.
This works fine but I thought it would make more sense to move Step #1 into its own useEffect construct above. Furthermore, in doing so, I could then also store the header object as its own Context property, which the GET call could then reference.
Unfortunately, I can now see from console log statements that when the GET call starts, the Auth Access Token has not yet been retrieved. So the refactoring attempt fails.
useEffect(() => {
const fetchData = async () => {
const config = {
headers: { "Authorization":
await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert('Error getting authorization token: '.concat(error))
})
}};
await axios.get('http://127.0.0.1:5000/some_path', config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert('Error getting data from endpoint: '.concat(error));
});
};
fetchData();
}, [myContextObject.some_data]);
Is there a way of refactoring my code into two useEffect instances such that the first one will complete before the second one starts?
You could hold the config object in a state. This way you can separate both fetch calls and trigger the second one once the first one finished:
const MyComponent = props => {
const myContextObject = useContext(myContext);
const [config, setConfig] = useState(null);
useEffect(() => {
const fetchData = async () => {
const config = {
headers: {
Authorization: await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert("Error getting authorization token: ".concat(error));
})
}
};
setConfig(config);
};
fetchData();
}, [myContextObject.some_data]);
useEffect(() => {
if (!config) {
return;
}
const fetchData = async () => {
await axios
.get("http://127.0.0.1:5000/some_path", config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert("Error getting data from endpoint: ".concat(error));
});
};
fetchData();
// This should work for the first call (not tested) as it goes from null to object.
// If you need subsequent changes then youll have to track some property
// of the object or similar
}, [config]);
return null;
};

How to end async mocha test inside a then()

I need to perform two consecutive POSTs in my test, and so I have the second nested in a callback inside of a then(). When I try running my test, I get this error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
Here is my code:
it('it should not create a user with a username already in the database', (done) => {
let user = {
"username": "myusername",
"password": "password"
};
chai.request(server)
.post('/user')
.send(user)
.then((response) => {
chai.request(server)
.post('/user')
.send(user)
.then((res) => {
res.should.have.status(406);
done();
});
});
});
I've tried increased my timeout limit, and that didn't work. What am I missing here?
You should always have a .catch.
.then(response => ...)
.catch(e => assert.equal(e.message, "Expecting data"))
Mocha supports promises, and it's best to use that support if you're testing promise-based code:
it('it should not create a user with a username already in the database', () => {
let user = {
"username": "myusername",
"password": "password"
};
return chai.request(server)
.post('/user')
.send(user)
.then((response) => {
return chai.request(server)
.post('/user')
.send(user)
.then(() => {
// We're _expecting_ a promise rejection, so throw if it resolves:
throw Error('should not be reached');
}, res => {
res.should.have.status(406);
});
});
});
Otherwise, the following may happen: the assertion (res.should.have.status(406)) may fail, which throws an error, which causes done never to get called, causing a timeout.
You can catch that assertion error with .catch and call done with the error, but you have to do that for all your tests, which is a bit tedious. It's also error-prone, because if, for some reason, a new error gets thrown inside the .catch, you end up with the same problem.
Ok, I figured it out. Found the answer on this post: When testing async functions with Mocha/Chai, a failure to match an expectation always results in a timeout
The reason it was never hitting my done calls was that it was hitting an uncaught exception and never returning. I wasn't providing a failure callback inside of my thens, and the Not Acceptable exception was being thrown.
Here is my fixed code:
it('it should not create a user with a username already in the database', (done) => {
let user = {
"username": "anh",
"password": "pass"
};
chai.request(server)
.post('/user')
.send(user)
.then((response) => {
chai.request(server)
.post('/user')
.send(user)
.then(() => {
throw Error('Something went wrong');
},
(res) => {
res.should.have.status(406);
done();
});
},
(res) => {
throw Error('Something went wrong');
});
});

Resources