XMLHttpRequest mock - Jest mockImplementation doesn't create class with specified methods - jestjs

I am trying to mock XMLHttpRequest but without success. Based on answers here I've managed to do following:
const xhrMockClass = () => ({
open: jest.fn(),
send: jest.fn(),
setRequestHeader: jest.fn()
})
window.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
test("App works", async () => {
const httpRequest = new XMLHttpRequest();
httpRequest.open('GET', 'www.google.com');
});
However my httpRequest object doesn't have open, send or setRequestHeader methods. Why the mocked implementation doesn't work as intended?
Tests fails with following error:
TypeError: httpRequest.open is not a function

Related

mocking a api call not returning data

Trying to mock the following api to return data.
I keep getting the error:
ReferenceError: Cannot access 'mockedApi' before initialization
Mock Code
const mockedApi = jest.fn();
jest.mock("../../../utils/api", () => ({
...jest.requireActual("../../../utils/api"),
get: jest.fn(),
}));
When I wrap it in a function then the response doesn't work.
const mockedApi = jest.fn();
jest.mock("../../../utils/api", () => ({
...jest.requireActual("../../../utils/api"),
get: () => mockedApi,
}));
when I do a log on the api its showing get as a function now that doesn't return anything. when is should be returning data if I was to use. ?
mockedApi.mockResolvedValueOnce({ data: 'hello });
Do I even need to use ...jest.requireActual("../../../utils/api")
I thought this would insure the original methods would not get mocked and only the once I add would be mocked like get. But this doesn't seem to be the case the entire file and all its methods get mocked ?
File been mocked
import axios from "axios";
const api = axios.create({
baseURL: process.env.REACT_APP_SPARQL_URL,
responseType: "json",
});
export const encode = (arr) => {
const urlencoded = new URLSearchParams();
arr.forEach((item) => {
urlencoded.append(...Object.keys(item), ...Object.values(item));
});
return urlencoded;
};
export default api;

How to mock Axios get response with jest

How do I mock an axios get response? This test is failing with the following error:
Error: expect(jest.fn()).toHaveReturnedWith(expected)
Expected: {"test": "test"}
Received: {}
Number of returns: 1
This is the test I'm running:
jest.mock('axios');
const axios = require('axios');
describe('GET /searchLocation', () => {
it('should return mock object', () => {
const mockResp = { test: 'test' };
axios.get.mockResolvedValue(mockResp);
axios.get('/');
expect(axios.get).toHaveBeenCalledWith('/'); //passes
expect(axios.get).toHaveReturnedWith(mockResp); //fails
});
});
I think replacing this:
axios.get.mockResolvedValue(mockResp);
With this:
axios.get = jest.fn(() => mockResp);
Should help you.
P.S.: this call expect(axios.get) won't wait for promise to resolve.

How do I mock (request-promise).defaults({ jar: true }) using Jest?

I am trying to write a test that uses a mock implementation of request-promise with defaults set.
In my code, I require request using
const request = require('request-promise').defaults({ jar: true });
In my test file, I have tried
const request = require('request-promise');
jest.mock('request-promise');
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
// This returns the error 'TypeError: request is not a function'
const request = require('request-promise').defaults({ jar: true });
jest.mock('request-promise');
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
// This returns the error 'TypeError: Cannot read property 'mockImplementation' of undefined'
const request = require('request-promise').defaults({ jar: true });
jest.mock('request-promise').defaults({ jar: true });
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
// This returns the error 'TypeError: jest.mock(...).defaults is not a function'
const request = require('request-promise').defaults({ jar: true });
jest.mock(('request-promise').defaults({ jar: true }));
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
// This returns the error 'TypeError: "request-promise".defaults is not a function'
My function looks like
const request = require('request-promise').defaults({ jar: true });
const getData = async function getData(
previousRequestResponse,
uniqueId,
) {
// Below function carries out some manipulation of previous response
const body = await getRequestBody(previousRequestResponse);
const response = await request(method, url, body, headers)
.then(response => JSON.parse(response.body))
.catch((err) => {
Logger.error('Failed');
Logger.error(`ERROR - ${err.message}`);
throw err;
});
const newResponse = manipulate(response);
return newResponse;
};
I want to mock request, so that response is a stubbed value and I can assert the correct value is returned at the end of the function.
If you want to mock the request-promise module you can do it using the jest.mock method and providing the factory parameter.
If you were to test your getData method you can create a factory that looks like:
jest.mock('request-promise', () => {
const mock = {
__esModule: true,
default: (method, url, body, headers) => Promise.resolve({
body: JSON.stringify([])
}),
defaults: () => mock.default
};
return mock;
});
With this code, your calls to request will always return an object with a body property that contains an empty array (as string so that the JSON.parse call in the getData method can parse it).
Appendix: Why the attempts you tried so far were not successful
Attempt 1
const request = require('request-promise');
jest.mock('request-promise');
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
Here you are calling jest.mock on the module without a factory parameter. Thus, jest will auto-mock all the module methods and they will all return undefined when called.
Hence, when in your code you import the module and call the defaults method:
const request = require('request-promise').defaults({ jar: true });
the request variable results in undefined. Later, when you call the request method it throws:
'TypeError: request is not a function'
Attempt 2
const request = require('request-promise').defaults({ jar: true });
jest.mock('request-promise');
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
The same logic of the previous attempt applies here. The request variable will be undefined and when you try to call the mockImplementation method it throws:
'TypeError: Cannot read property 'mockImplementation' of undefined'
Attempt 3
const request = require('request-promise').defaults({ jar: true });
jest.mock('request-promise').defaults({ jar: true });
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
Here you are calling the method defaults on the returning value of the call to jest.mock. As the call to jest.mock does not return your mocked version of the request-promise module, it does not contain a defaults method. So, when you call that method it throws:
'TypeError: jest.mock(...).defaults is not a function'
Attempt 4
const request = require('request-promise').defaults({ jar: true });
jest.mock(('request-promise').defaults({ jar: true }));
request.mockImplementation(() => Promise.resolve(JSON.stringify(someVar)));
In this attempt you are trying to call a defaults method from the string request-promise. Strings have no such method, so calling it throws:
'TypeError: "request-promise".defaults is not a function'

Issues mocking return value of Axios post call for unit test

Trying to mock an Axios call to unit test a token response from our identity software. Axios is not getting called at all and because of that my return is always undefined.
I've tried changing up Axios call to axios.post and changing the way I've mocked this more times then I can count. I don't believe like I should have to install another mocking framework just for Axios to mock this one function.
Implementation:
async getAuthToken() {
const oauthUrl = process.env.OAUTHURL;
const oauthAudience = process.env.OAUTHAudience;
const oauthUsername = process.env.OAUTHUSERNAME;
const oauthPassword = process.env.OAUTHPASSWORD;
let urlForAuth = oauthUrl
urlForAuth = urlForAuth + '/as/token.oauth2?';
urlForAuth = urlForAuth + 'grant_type=client_credentials';
urlForAuth = urlForAuth + '&aud=' + oauthAudience + '/';
urlForAuth = urlForAuth + '&scope=' + oauthAudience + '/.read';
const options = {
method: 'POST',
url: urlForAuth,
headers: {
'Authorization': "Basic " + Buffer.from(oauthUsername + ":" + oauthPassword).toString("base64")
},
responseType: 'json'
};
try{
let response = await axios(options);
return response.data.access_token;
}
catch(e){
console.log(e);
throw e;
}
}
Test Case:
test('token Is Returned', async () => {
expect.hasAssertions();
let Response = `{
"access_token": "thisisatoken",
"token_type": "Bearer",
"expires_in": 3599
}`;
axios = jest.fn(() => Promise.resolve());
axios.mockImplementationOnce(() =>
Promise.resolve({
data: Response
})
);
let response = await AuthService.getAuthToken();
expect(axios).toHaveBeenCalledTimes(1);
expect(response).toEqual("thisisatoken");
});
The error I am getting is
Expected mock function to have been called one time, but it was called zero times.
When I debug the data element on the response contains the following:
data:"Copyright (c) 2019 Next Small Things\n"
That is no where in my code. help.
You cannot mock things this way. Actually you are mocking axios only for your test's code but not for component under test that import's axios on its own.
You need to mock module properly and you have actually plenty of options:
provide ready-to-use mock in __mocks__ folder
call jest.mock('axios') to get autogenerated mock(each exported member will become jest.fn automatically)
provide factory for mock jest.mock('axios', () => { .... return object like it all are exported from file ... })
Also you need to import axios into your test to access it:
import axios from 'axios';
jest.mock('axios');
test('token Is Returned', async () => {
expect.hasAssertions();
let Response = `{
"access_token": "thisisatoken",
"token_type": "Bearer",
"expires_in": 3599
}`;
axios.mockReturnValue(() =>
Promise.resolve({
data: Response
})
);
let response = await AuthService.getAuthToken();
expect(axios).toHaveBeenCalledTimes(1);
expect(response).toEqual("thisisatoken");
});
Beware of few things:
jest.mock is hoisted to the top even if it's declared somewhere really deep in test's code. So it's better to place all jest.mock at the top of the file - because it anyway works this way - and this way another developer would not be confused if it's mocked or not.
if using __mocks__ folder with auto mock you better inject jest.fn() in advance - most times you will like to check if part of mock has been called and with what arguments
jest.mock cannot refer to any sibling variable except those one with name starting from mock.... See Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error with really good explanation.
it'd be hard(near to impossible) to unmock module partially. so consider your test either mock some module or does not mock at all to test it.

mock nodemailer.createTransport.sendMail with jest

I have some code which uses the nodemailer module.
In the router (router.js), I have
const transporter = nodeMailer.createTransport(emailArgs);
Then inside the route (/login) I have:
...
return transporter.sendMail(mailOptions);
I'm trying to test this route using the jest testing framework. I'm having some trouble mocking out the call to sendMail. I read this nice blogpost about how to use jest mocking, but I'm getting this error:
TypeError: Cannot read property 'sendMail' of undefined
And indeed when I check the value of transporter it's undefined.
Here is my testing code (which doesn't work):
import request from "supertest";
import router from "./router";
jest.mock("nodemailer");
describe("", () => {
...
test("", async () => {
// 1 - 200 status code; 2 - check email was sent
expect.assertions(2);
const response = await request(router)
.post("/login")
// global variable
.send({ "email": email })
.set("Accept", "application/json")
.expect("Content-Type", /json/);
// should complete successfully
expect(response.status).toBe(200);
// TODO not sure how to express the expect statement here
});
});
So my question is how do I mock out a method of an instance of a class which is returned by a module?
I ran into the same problem and found a solution. Here is what I've discovered:
With jest.mock("nodemailer"); you tell jest to replace nodemailer with an auto-mock. This means every property of nodemailer is replaced with an empty mock function (similar to jest.fn()).
That is the reason why you get the error TypeError: Cannot read property 'sendMail' of undefined.
In order to have something useful, you have to define the mock function of nodemailer.createTransport.
In our case we wan't to have an object with a property sendMail. We could do this with nodemailer.createTransport.mockReturnValue({"sendMail": jest.fn()});. Since you may want to test if sendMail was called, it is a good idea to create that mock function before hand.
Here is a complete example of your testing code:
import request from "supertest";
import router from "./router";
const sendMailMock = jest.fn(); // this will return undefined if .sendMail() is called
// In order to return a specific value you can use this instead
// const sendMailMock = jest.fn().mockReturnValue(/* Whatever you would expect as return value */);
jest.mock("nodemailer");
const nodemailer = require("nodemailer"); //doesn't work with import. idk why
nodemailer.createTransport.mockReturnValue({"sendMail": sendMailMock});
beforeEach( () => {
sendMailMock.mockClear();
nodemailer.createTransport.mockClear();
});
describe("", () => {
...
test("", async () => {
// 1 - 200 status code; 2 - check email was sent
expect.assertions(2);
const response = await request(router)
.post("/login")
// global variable
.send({ "email": email })
.set("Accept", "application/json")
.expect("Content-Type", /json/);
// should complete successfully
expect(response.status).toBe(200);
// TODO not sure how to express the expect statement here
expect(sendMailMock).toHaveBeenCalled();
});
});
To mock nodemailer module I do
jest.mock('nodemailer', () => ({
createTransport: jest.fn().mockReturnValue({
sendMail: jest.fn().mockReturnValue((mailoptions, callback) => {})
})
}));
works like a charm
you can also define a mocked function if you need to evaluate .toBeCalledWith() etc:
const sendMailMock = jest.fn()
jest.mock('nodemailer', () => ({
createTransport: jest.fn().mockImplementation(() => ({
sendMail: sendMailMock,
})),
}))
well I still wanted my mailer to work and returning undefined was not working, so I had to change sendMailMock to this:
const sendMailMock = jest.fn((mailOptions, callback) => callback());
This worked for me
Create a mock file at the directory mocks/nodemailer.js (See Jest Manual Mock for reference)
Add the following code to the file. The createTransport method needs to return a response that has a method sendMail for it to work. So see the code used below
class CreateTransportClass {
sendMail(){
//console.log("mocked mailer");
}
}
const createTransport = ()=>{
return new CreateTransportClass()
}
module.exports = {
createTransport
}
In the jest config file (jest.config.js) add the file path to the testPathIgnorePatterns like this:
{
testPathIgnorePatterns: ["/__mocks__/nodemailer.js"],
}
This should work perfectly.

Resources