I am trying to mock fetch API with Jest but facing issue that assertions run before the fetch is resolved so tests always fail. Here is the sandbox : https://codesandbox.io/s/jest-testing-2evvr
I also tried async/await but still same issue :
it("should update result on successful fetch", async () => {
const updateSearchResult = jest.fn();
await handleSearch("api");
expect(updateSearchResult).toHaveBeenCalledTimes(1);
});
How can fix this , so that code runs after finally block of fetch . thanks
handleSearch doesn't return a promise which is an antipattern, so await handleSearch("api") doesn't affect anything. It will also fail if no callback is provided. Native promises allow to provide a non-function to then, in this case it's ignored. Since a callback should receive same parameters as then callback, it should be:
function handleSearch(searchQuery, callback) {
return fetch(searchQuery)
.then(callback)
...
}
well this is my approach to mock a async function :
let resolve , reject;
jest.mock("../module,()=>({
...jest.requireActual("../module"),
handleSearch:jest.fn(()=> new Promise((res,rej)=>{
resolve = res;
reject = rej
}))
}))
it("should update result on successful fetch", async () => {
const updateSearchResult = jest.fn();
await handleSearch();
resolve("api")
expect(handleSearch).toHaveBeenCalledTimes(1);
});
Related
I'm trying to run the mockttp with cypress. I used the example that's listed in project github. I changed the port to run on 3000 but I am getting an error saying Cannot add rules before the server is started.
/*global cy:true,before:true*/
/// <reference path="../../node_modules/cypress/types/index.d.ts" />
const superagent = require("superagent");
const mockServer = require("mockttp").getLocal();
describe('mockttp test' , () => {
beforeEach(() => {
mockServer.start(3000);
});
afterEach(() => {
mockServer.stop();
});
it("lets you mock requests, and assert on the results", async () => {
// Mock your endpoints
const mockedPath = mockServer.forGet("/mocked-path");
// ERROR OCCURS HERE
await mockedPath.thenReply(200, "A mocked response");
// Make a request
const response = await superagent.get("http://localhost:3000/mocked-path");
// Assert on the results
expect(response.text).to.equal("A mocked response");
});
});
You need to wait until the server is actually started before running your test, by waiting for the promises returned by mockServer.start() (and by .stop()).
You can either make your beforeEach & afterEach functions async and then await those lines, or you can just add return to return the promise so that Mocha waits for them automatically.
fetchMock.get(`http://localhost:8080/data/v1/shopping-cart.json#/`).then(res => {
console.log('response ', res);
});
I am passing http://localhost:8080/data/v1/shopping-cart.json#/ as a base URL to the fetchMock.get() method and getting the following output:
fetch-mock: Invalid parameters passed to fetch-mock
What could be a reason?
First, you actually misuse fetchMock(). Check their docs on details. It's expected you to pass url and callback as part of .get():
fetchMock.get('http://localhost:8080/data/v1/shopping-cart.json', (url, opts) => {
return {body: 'aaa', status: 200}
});
fetch('http://localhost:8080/data/v1/shopping-cart.json').then(res => console.log(res));
Second you should not use hash part(#/ in your case) for URL mocked. Browser does not send this part to server so I believe your mock may not work at all(until fetch-mock strips this part for mocks under the hood, but I'd not rely on that).
You can mock the fetch method to return mock promise, as below:
in your test case:
window.fetch = jest.fn().mockImplementation(() =>
new Promise((resolve) =>
resolve({ /** dummy response object here */ })
)
);
and then when you call the action with async calls, the result should be equal to what you were doing in the then handler (success handler)
similarly, you can reject the promise to test the catch handler.
Hope it helps.
I am writing a grapqhl server that has a simple logout mutation. Everything works as expected when I run the server and I can log out by destroying the session and clearing the cookie just fine.
Here is the resolver:
export default async (root, args, context) => {
console.log("THIS WILL LOG")
await new Promise((res, rej) =>
context.req.session.destroy(err => {
if (err) {
return rej(false);
}
context.res.clearCookie("qid");
return res(true);
})
);
console.log("NEVER HERE BEFORE TIMEOUT");
// 4. Return the message
return {
code: "OK",
message: "You have been logged out.",
success: true,
item: null
};
};
I am attempting to write a simple test just to verify that the req.session.destroy and res.clearCookie functions are actually called. At this point I AM NOT attempting to test if a cookie is actually cleared, as I am not actually starting up the server, I am just testing that the graphql resolver was ran correctly and that it called the right functions.
Here is a portion of my test:
describe("confirmLoginResolver", () => {
test("throws error if logged in", async () => {
const user = await createTestUser();
const context = makeTestContext(user.id);
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
context.res.clearCookie = jest.fn();
// this function is just a helper to process my graphql request.
// it does not actually start up the express server
const res = await graphqlTestCall(
LOGOUT_MUTATION, // the graphql mutation stored in a var
null, // no variables needed for mutation
null // a way for me to pass in a userID to mock auth state,
context // Context override, will use above context
);
console.log(res);
expect(context.req.session.destroy).toHaveBeenCalled();
// expect(res.errors.length).toBe(1);
// expect(res.errors).toMatchSnapshot();
});
});
Again, everything works correctly when actually running the server. The problem is that when I attempt to run the above test, I always get a jest timeout:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
The reason is that the await section of above resolver will hang because it's promise.resolve() is never being executed. So my console will show "THIS WILL LOG", but will never get to "NEVER HERE BEFORE TIMEOUT".
I suspect I need to write a better jest mock to more accurately simulate the callback inside of context.req.session.destroy, but I can not figure it out.
Any ideas how I can write a better mock implementation here?
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
Is not cutting it. Thoughts?
Try
context.req.session.destroy = jest
.fn()
.mockImplementation((fn) => fn(false));
I'm quite new to node and express.
And have been trying to write test code using mocha, chai and chai-http.
Here's the part of source code.
const mongoose = require('mongoose'),
User = require('../../models/user');
const mongoUrl = 'mongodb://xxxxxxxxxxx';
describe('/test', function() {
before('connect', function() {
return mongoose.createConnection(mongoUrl);
});
beforeEach(async function(done) {
try {
await User.remove({}); // <-- This doesn't work
chai.request('http://localhost:3000')
.post('/api/test')
.send(something)
.end((err, res) => {
if (err) return done(err);
done();
});
} catch (error) {
done(error);
}
});
});
And I get the following error with "npm test"(nyc mocha --timeout 10000 test/**/*.js).
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I confirmed the database connection works properly from log.
And seems I get the timeout error with await User.remove({}).
I've also tried different methods such as a User.save()
But, I got the same error.
Do I need to do something special with database model and connection?
This is all pretty simple.
To avoid the error you must not use both done and async/await in Mocha at the same time. Either use async/await and remove both done as function parameter and done() call. Or use done. Then remove both async/await. See the example tests below for each case.
Use try/catch with async/await as you would normally use it with synchronous code.
Following are the most basic Mocha tests with both async/await and done approaches testing the same basic HTTP server endpoint.
This is async/await approach.
it('with async/await', async function() {
const res = await chai.request(server)
.get('/')
.send();
assert.equal(res.status, 200);
});
This is done approach.
it('with done & callbacks', (done) => {
chai.request(server)
.get('/')
.end((err, res) => {
assert.equal(res.status, 200);
done();
});
});
See the full test file snippet.
For working example additionally spin basic Express server as the tests counterpart in src/app.js.
See Chai HTTP plugin docs for more info on what you can do with request testing.
This is it.
I had the same problem and have not found a way to get any promises that involve mongoose working with Mocha/Chai.
What may help you is doing what I did and putting your mongoose code in a script so you can run it with node <scriptfile>.js. You can use that to confirm it's working properly by itself. In my test, the mongoose operation finished in less than a second. You can also call that file from another (non-test related) to confirm it executes properly and returns a promise. You can see from my example how to make sure you close db properly. Partial example:
...
db.close();
return new Promise((resolve) => {
db.on('disconnected', () => {
console.log('***************************************Mongoose CONNECTION TERMINATED');
resolve('user ready');
});
});
...
You may also find some clues by looking at the following issues here and here.
The work around that I did after wasting too much time trying to figure out this crazy behavior was to perform my mongoose needs in a route. I wrap each request that needs to use it in the end block of the extra chai.request... or use async. Example:
describe('something', () => {
it('should do something and change it back', async () => {
try {
// change user password
let re1 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
});
expect(re1.statusCode).to.equal(200);
// change password back since before hook not working
let re2 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
passwordNew: 'password',
passwordConfirm: 'password',
});
expect(re2.statusCode).to.equal(200);
} catch (error) {
// error stuff here
}
});
Note that using the try/catch syntax above will cause test that should normally fail to show passing and the results will be caught in the catch block. If you want to avoid that, just remove the try/catch.
How did you implement ./models/user? await only works if User.remove() returns a promise, not if it expects a callback. I would add debug information to your User.remove() function to see where it gets stuck.
I'm new to redux and programming in general and am having trouble wrapping my head around certain unit testing concepts.
I have some async actions in redux, which involve calls to a third party API (from the 'amazon-cognito-identity-js' node module).
I have wrapped the external API call in a promise function, and I call this function from the 'actual' action creator. So for testing I just want to stub the result of externalAWS() function so that I can check that the correct actions are being dispatched.
I'm using redux-thunk for my middleware.
import { AuthenticationDetails,
CognitoUser
} from 'amazon-cognito-identity-js';
export function externalAWS(credentials) {
//This is required for the package
let authenticationDetails = new AuthenticationDetails(credentials);
let cognitoUser = new CognitoUser({
//Construct the object accordingly
})
return new Promise ((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
resolve(result);
},
onFailure: error => {
reject(error)
}
})
}
}
export function loginUser(credentials) {
//return function since it's async
return dispatch => {
//Kick off async process
dispatch(requestLogin());
externalAWS(credentials)
.then((result) => {
dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
})
.catch((error) => {
dispatch(failedLogin(error.message, etc))
})
}
}
I don't have any test code yet because I am really not sure how to approach this. All the examples deal with mocking a HTTP request, which I know is
what this boils down to, so am I supposed to inspect the HTTP requests in my browser and mock them out directly?
It's further complicated by the fact that the second argument of authenticateUser is not even a plain callback, but an object with callbacks as it's values.
Can anyone offer some advice on whether my intention in unit testing the async function is correct, and how I should approach it? Thank you.
Edit: I'm testing in Jest.
Edit2: Request Headers
First POST request,
Second POST request
Edit3: Split the function, trying my best to isolate the external API and create something that is 'easily mock/stub-able'. But still running into issues of how to properly stub this function.
Redux thunk gives you the ability to dispatch future actions within the context of a main action that kicks off the process. This main action is your thunk action creator.
Therefore tests should focus on what actions are dispatched within your thunk action creator according to the outcome of the api request.
Tests should also look at what arguments are passed to your action creators so that your reducers can be informed about the outcome of the request and update the store accordingly.
To get started with testing your thunk action creator you want to test that the three actions are dispatched appropriately depending on whether login is successful or not.
requestLogin
receiveLogin
failedLogin
Here are some tests I wrote for you to get started using Nock to intercept http requests.
Tests
import nock from 'nock';
const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
const fakeCredentials = {
username: 'fakeUser'
token: '1234'
}
it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
})
});
it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
})
});
So I figured it out in the end.
First, I had to require() the module into my test file (as opposed to ES6 import). Then I removed the promise for now since it was adding a layer of complexity and combined everything into one function, let's call it loginUser(). It is a redux async action, that dispatches one action upon being called, and then a success or failure action depending on the result of the API call. See above for what the API call looks like.
Then I wrote the test as follows:
const CognitoSDK = require('/amazon-cognito-identity-js')
const CognitoUser = CognitoSDK.CognitoUser
//Set up the rest of the test
describe('async actions', (() => {
it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
callback.onSuccess(mockResult)
})
store.dispatch(loginUser(mockData))
expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
}))
}))
So basically once requiring the module in, I mocked it in Jest and did a mock implementation too, so that I could access the onSuccess function of the callback object.