Testing a Jest method that has multiple promises - javascript

I am attempting to write a test for a service in my app.
async post(url, params, headers) {
const csrfToken = await this.getCsrfToken().then(res => res.data);
headers.headers['X-CSRF-TOKEN'] = csrfToken;
// console.log(params);
return this.http.post(url, params, headers);
}
The issue I am encountering is I am getting an error that data is not defined. I believe this refers to the csrfToken call (which is just another API call to get this token to append to the header).
I'm not entirely sure how to mock that constant inside jest so I can actually get to my post call. Is there an easy way in jest?

You shouldn't try to mock the constant, you should mock the getCsrfToken instead. Try something like:
import { getCsrfToken, post } from MyClass
it('should work', () => {
// mock method on your class
myMock = jest.fn()
myMock.mockReturnValueOnce(Promise.resolve({
data: {
fakeCsrf
}
})
MyClass.csrfToken = myMock
post('/test', {}, {})
expect(...);
});

Related

mocking a promise in jest mock module factory

I'm writing some units tests and need to mock a named export from a file, but it doesn't seem to work when the export is a function that returns a promise.
For example, I have a module located at lib/api.js that exports a single function called getFoo, which itself returns axios.get('/foo').
In my tests, I want to mock the return value of getFoo to be a resolved promise with the foo payload, so I mock using a module factory:
import * as api from 'lib/api';
jest.mock('lib/api', () => ({
getFoo: jest.fn(() => Promise.resolve('foo')),
});
and then in my test I want to assert that getFoo was called as part of larger process of chained promises.
it('getBar should call getFoo', () => {
return getBar().then(() => {
expect(api.getFoo).toHaveBeenCalled();
});
})
The error I get here is cannot read property 'then' of undefined, as though the jest function used in the mocked module factory is not returning the promise.
If the return value for getFoo is not a promise, it works as expected. If I use jest.fn() as the value for getFoo, and then in the test itself mock the implementation...
it('getBar should call getFoo', () => {
api.getFoo.mockImplementation(() => Promise.resolve("foo"));
return getBar().then(() => {
expect(api.getFoo).toHaveBeenCalled();
});
})
...then it works. My understanding is that the jest.fn(() => {}) should just be a shorter way of defining the implementation, so I am not sure why I am getting the error. Any help appreciated.
Thanks.
More Info:
The lib/api looks like this...
import axios from 'axios';
export const getFoo = (host: string, accessToken: string): Promise<{}> =>
axios.get(`${host}/foo`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
})
// return foo
.then((response) => response.data)
.catch(() => {
throw new Error('Failed to load foo');
});
When I console log the mocked getFoo function while the test is running, it shows up as...
getFoo function mockConstructor() {
return fn.apply(this, arguments); }
First thing, you need to mock it before importing it:
jest.mock('lib/api');
import * as api from 'lib/api';

Nock not intercepting http requests

I am currently using node-fetch and nock for an express server that sits on top of an angular project.
I have the following middleware that is making a call to an api:
export const middleware = async(response: any) => {
try {
const result = await fetch(url).then(res => res.json())
return response.status(200).json(result);
} catch(err) {
logger.error({eventId: 'get-content', err});
return response.status(500)
}
}
and my test is as follows:
describe('API service', () => {
let response, scope;
beforeEach(() => {
response = {
status(s) { this.statusCode = s; return this; },
json(result) { this.res = result; return this; },
};
})
afterEach(() => {
nock.restore();
})
it('should return a 200 response from successful call api', (done) => {
scope = nock(url)
.get(/.*/)
.reply(200, {data: 'content'})
middleware(response).then(data => {
expect(response.status).toEqual(200);
expect(response.data).toEqual('content');
scope.isDone();
done();
})
})
})
However, nock is not mocking the data response from the middleware function. Instead, I'd have to use scope to access its parameters.
The middleware function acts as if nock never mocked its response. Why is this occurring? Am I missing a configuration?
I am serving my tests using karma runner.
Nock works by overriding Node's http.request function. Also, it overrides http.ClientRequest too to cover for modules that use it directly.
https://github.com/nock/nock#how-does-it-work
Unfortunately it appears fetch does not make use of the http.request or http.ClientRequest meaning the requests are never intercepted by nock.
A better approach may be to mock fetch with a library such as fetch-mock.

How do I mock async fetches with jest?

I've been struggling to figure out how to properly test this code for days :(
const request = require('superagent');
const getDog = () => {
return request.get('https://dog.ceo/api/breeds/image/random');
};
it('resolves', () => {
// Expect mocked response
});
it('rejects', () => {
// Expect mocked response
});
In most cases, your code gets some value from API parses it and makes some stuff with it.
As a result, you don't want to make real API call and mock it instead.
There are a couple of ways to do it. One of the possible is to mock the only method on the superagent library.
// tell jest not to mock superagent because we'll mock the only method
jest.unmock('superagent');
const request = require('superagent');
const getDog = () => {
return request.get('https://dog.ceo/api/breeds/image/random');
};
it('resolves', () => {
// mock the get request to resolve object
request.get = jest.fn().mockResolvedValue({
message: 'Your message'
});
// Expect mocked response
expect.assertions(1);
return expect(getDog()).resolves.toEqual({
message: 'Your message'
});
});
it('rejects', () => {
// mock the get request to reject object
request.get = jest.fn().mockRejectedValue({
message: 'Your error'
});
// Expect mocked response
expect.assertions(1);
return expect(getDog()).rejects.toEqual({
message: 'Your error'
});
});
I used expect.assertions(1), there is a reason:
This is often useful when testing asynchronous code, in order to make
sure that assertions in a callback actually got called.
There are links that can help you:
mockFn.mockResolvedValue(value)
,
.rejects
One solution is to use a library such as nock or fetch-mock to mock out the HTTP response to your request.

fetch-mock calls the actual API instead of mocking the request

Given this code:
const fetch = require('isomorphic-fetch')
module.exports = function suggestions (...args) {
// some error handling code
return fetch(MY_ENDPOINT)
}
I have found that when using fetch-mock like this:
require('isomorphic-fetch')
const fetchMock = require('fetch-mock/es5/server')
fetchMock.mock(MY_ENDPOINT, [{}, {}])
describe('My spec', () => {
it('fakes a request to my endpoint', async () => {
const myData = await myCode(...args) // calls fetch with my endpoint
expect(myData).toEqual([{}, {}])
})
})
I would expect the mock to give me the response I set up which is [{},{}], instead it calls the real API and returns the response from it.
Also, if I try to use fetch-mock instead of fetch-mock/es5/server I get the error:
/Users/localuser/lendi/ldp-domain-integration/node_modules/fetch-mock/src/lib/fetch-handler.js:57
FetchMock.generateResponse = async function (response, url, opts) {
^^^^^^^^
SyntaxError: Unexpected token function
I use jest with babel and babel-jest along with node v6.13.0.
The reason for the Syntax error is that async functions are not supported in Node version 6.
Try updating Node to the latest version.

Mocking an ES6 class function with Jest

I have a question about how can I mock an ES6 class instance using Jest which is used by the method I actually want to test.
My real case is trying to test a Redux async action creator that make a request and dispatch some action depending on the request result.
This is a simplified example of the use case :
// communication.js
// An exported ES6 class module with proxy to the request library.
import post from './post';
export default class communication {
getData(data, success, error) {
const res = post(data);
if(res) {
success();
} else {
error();
}
}
}
// communicatorAssist.js
// A redux async function using communication.js
import communication from './communication';
// ...
export function retrieveData() {
return dispatch => {
const data = { name: 'michel'};
communication.getData(data,
(res) => dispatch(successAction(res)),
(res) => dispatch(errorAction(res));
}
}
// communicatorAssist.test.js testing the communicatorAssist
import { retrieveData } from 'communicatorAssist';
// communication.getData should call success callback
// for this test.
it('Should call the success callback', () => {
retrieveData();
// Assert that mocked redux store contains actions
});
// communication.getData should call error callback
// for this test.
it('Should call the error callback', () => {
retrieveData();
// Assert that mocked redux store contains actions
});
What I want is mocking the communication class in the test and change the behaviour of the getData() function in each test to call success and error callbacks without any call to post method.
I only success to mock the getData() function for the whole test file with this snippet at the top of it :
import communication from '../communication'
jest.mock('../communication', () => (() => ({
getData: (success, error) => success()
})));
but I can't switch between implementation in different test case.
I figure that something using .mockImplementation() could do the stuff but I can't make this work in my case (I saw examples using it for module exporting functions but not for classes ).
Does anyone have an idea ?
Edit :
I forgot a part in the code exemple : the communication class instance creation which I think is the "problem" for mocking it :
const com = new communication();
If com is instanciated at a global level in the communicatorAssist.js file : it fail with communication.getData is not a function error.
But if I set the instanciation inside the retrieveData() function, Andreas Köberle snippet work's fine :
import communication from '../communication'
jest.mock('../communication', () => jest.fn());
communication.mockImplementation(
() => ({
getData: (success, error) => success()
})
)
(jest.mock() factory parameter need to return a function not directly jest.fn)
I don't know why it's not working using a file global scope instance.
You need to mock the module with jest.fn() then you can import it and change the behaviour of it using mockImplementation:
import communication from '../communication'
jest.mock('../communication', jest.fn());
communication.mockImplementation(
() => ({
getData: (success, error) => success()
})
)

Categories

Resources