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'
Related
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;
When I try to hit an endpoint with postman everything works, so I assume the problem is probably with my axios request as when logging req.headers.cookies on server after performing this axios request the value is undefined.
Cookies in browser work as well they are set correctly.
When i performed this request in postman the value of req.headers.cookie was fine and the request has been performed without any errors.
Client code:
useEffect(() => {
(async () => {
const res = await axios.post('http://localhost:4000/refresh_token', {
withCredentials: true,
});
})();
}, []);
Server code (endpoint function):
export const validateRefreshToken = async (req, res) => {
console.log(req.headers.cookie); // undefined
const { token } = parse(req.headers.cookie);
...
};
Error message: TypeError argument str must be a string.
This error points to the parse function.
Has anyone experienced this before? Any ides on how I can fix this issue?
With Axios POST, 1st arg is the url, 2nd arg is data and the 3rd arg is for options.
Provide withCredentials: true in the 3rd argument of Axios.post
useEffect(() => {
(async () => {
const res = await axios.post('http://localhost:4000/refresh_token', {} ,{
withCredentials: true,
});
})();
}, []);
I have written a following test case using jest to test REST API:
test('Should return module for given module name' , async () =>{
const response = await request(app)
.get('/modules')
.set('Authorization', 'Bearer ' +User1.tokens[0].token)
.send('modulename','Shark')
.expect(200)
expect(response.body.name[0]).toBe('Shark')
Corresponding node js route is
var modulename = req.query.modulename;
console.log(modulename)
const modules = await LuaModule.findByName(modulename)
res.send(modules)
}
.expect(200) succeeds however expect(response.body.name).toBe('Shark') fails even though API returns JSON response having 'name' field with value as 'Shark'. Jest throws following error:
Expected: Shark
Received: undefined
Server-Side Route-handler:
router.get('/modules', auth, async(req, res) => {
var modulename = req.query.modulename;
console.log(modulename);
const modules = await LuaModule.findByName(modulename);
res.send(modules);
}
Any suggestion regarding this will be helpful.
As you're expecting a query parameter to be set in the request on the server side, you need to make sure it's set correctly by supertest:
const response = await request(app)
.get('/modules')
.set('Authorization', 'Bearer ' +User1.tokens[0].token)
.query({modulename: 'Shark'}); // instead of .send(...)
...
Also since you're returning an array (and not an object) you need to access the name property of the object at the first index as:
expect(response.body[0].name).toBe('Shark')
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.
I want to add data to the request object for each request. Hence I subscribed to the onRequest event and am doing:
server.ext('onRequest', (request, reply) => {
console.log('I AM CALLED');
const marketCode : Market = request.params["market-code"];
const operatorCode : Operator = request.params["operator-code"];
request.operatorContext = new OperatorContext(
operatorEndpointEnv,
operatorCode,
marketCode
);
console.log(request.operatorContext); // will be defined
return reply.continue();
});
Yet when my controller:
function apiStatus(req, reply) {
console.log(req.operatorContext); // will be undefined
reply.status(204).end();
}
recieves the call, the request is just plain has request.operatorContext as undefined, yet it set within the context of the adding the extension it is defined, as I can see it logged:
I AM CALLED
OperatorContext { environment: 'test', operator: undefined, market: undefined }
undefined
How to load my custom data to a request object?