How to mock the api call in jest for saga test - jestjs

I am writing tests to test my saga. Can anyone guide me how I can change the code below so that I can mock the api call? I don`t want to test real data.
import { call, put } from 'redux-saga/effects';
import { API_BUTTON_CLICK_SUCCESS, } from './actions/consts';
import { getDataFromAPI } from './api';
it('apiSideEffect - fetches data from API and dispatches a success action', () => {
const generator = apiSideEffect();
expect(generator.next().value)
.toEqual(call(getDataFromAPI));
expect(generator.next().value)
.toEqual(put({ type: API_BUTTON_CLICK_SUCCESS }));
expect(generator.next())
.toEqual({ done: true, value: undefined });
});
The getDataFromAPI()
import axios from "axios";
export const getDataFromAPI =(
method: string,
url: string,
path: string,
data?: any
) =>{
switch (method) {
case "create": {
return axios
.post(url + path, data, {
headers: {
Accept: "application/json",
"content-type": "application/json"
}
})
.catch(error => {
throw error.response;
});
}
I have tried to use
jest.mock('../../src/Utilities/api');
const { callApi } = require('../../src/Utilities/api');
callApi.mockImplementation( () => console.log("some api call"));
I am having the error
TypeError: Cannot read property 'mockImplementation' of undefined
at Object.<anonymous> (src/Payments/PaymentSagas.spec.ts:10:17)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)

I usually do
import * as apis from '../../src/Utilities/api';
jest.spyOn(api, "callApi");
api.callApi.mockImplementation(/* your mock */);
easily exportable as a per-se function
export function spyUtil(obj, name, mockFunction = undefined) {
const spy = jest.spyOn(obj, name);
let mock;
if (mockFunction) {
mock = jest.fn(mockFunction);
obj[name].mockImplementation(mock);
}
return { spy, mock };
}
and consumable, in your test
spyUtil(apis, "callApi", jest.fn())

Related

When run test, TypeError: Cannot destructure property 'travelDatas' of '(0 , _GetTravelDatas.getTravelDatas)(...)' as it is undefined

When I run test, it show TypeError: Cannot destructure property 'travelDatas' of '(0 , _GetTravelDatas.getTravelDatas)(...)' as it is undefined.
As you see the screenshot: unit test
There isn't any console error or warning.
Could anyone help please
travelListTest.spec.js
import { mount, shallowMount } from '#vue/test-utils'
import TravelList from '../../src/components/TravelList.vue'
import { getTravelDatas } from '../../src/composables/GetTravelDatas'
import ElementPlus from 'element-plus'
const wrapper = shallowMount(TravelList, {
global: {
plugins: [ElementPlus]
}
})
jest.mock('../../src/composables/GetTravelDatas')
describe('TravelList Test', () => {
test('click more will call GoToTravelDetailPage', () => {
wrapper.vm.GoToTravelDetailPage = jest.fn()
console.log(wrapper.html())
wrapper.find('.el-button').trigger('click')
expect(wrapper.vm.GoToTravelDetailPage).toHaveBeenCalled()
})
})
TravelList.vue
.....
<script>
import { ref } from '#vue/reactivity';
import { useRouter } from "vue-router";
import { getTravelDatas } from '../composables/GetTravelDatas'
export default {
name: 'TravelList',
setup() {
const { travelDatas } = getTravelDatas();
const router = useRouter();
function GoToTravelDetailPage(acctractionId) {
router.push({ path: `/travelDetail/${acctractionId}` })
}
return { travelDatas, GoToTravelDetailPage };
},
};
</script>
GetTravelDatas.js
import axios from "axios";
import { ref } from '#vue/runtime-core';
export function getTravelDatas() {
const travelDatas = ref([])
axios.get('https://localhost:5001/MyTravel/GetTravelData')
.then((response) => {
if (!response.data.success) {
alert(response.data.errorMessage)
}else{
travelDatas.value = response.data.travelDetail
}
}).catch((error) => {
alert('Unexpected Error: ', error.message)
console.log(error)
});
return { travelDatas }
}
You are mocking the GetTravelDatas module with an auto-mock version by calling:
jest.mock('../../src/composables/GetTravelDatas')
That means that all the methods exported from that module will be mocked (the real code of the method will not be called) and the mocked version will return undefined when called.
In the code you are testing you then have:
const { travelDatas } = getTravelDatas();
Reading the travelDatas property from undefined is causing the error you are seeing.
You should mock the getTravelDatas method so that it returns the appropriate data. For example, returning an empty array would look like:
getTravelDatas.mockReturnValue([]);

Create IncomingMessage test dummy in Jest

I am trying to write a unit test for a function that takes IncomingMessage as a parameter. I understand it is a stream but I am unsure how to create a basic test dummy as the stream causes my tests to timeout
: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
My class
import { IncomingMessage } from 'http';
export class MyClass
example(request: IncomingMessage): void {
console.log(request.get('hello?'))
}
}
My test
it('test', () => {
const myclass = new MyClass();
const request = {
get: () => 'hello!'
} as unknown as IncomingMessage
const response = myclass.example(request);
expect(response).toEqual('hello!');
});
I assume I need to close the stream so how can I do that when casting a 'dummy' object to type IncomingMessage?
I tried using
const request = {
get: () => 'hello'
} as unknown as IncomingMessage
request.setTimeout(1)
but then I get an error that the function is not found, which makes sense since the dummy is basically an empty object with only one mocked function
TypeError: request.setTimeout is not a function
In this case I assume I need to actually create a real IncomingMessage? But I cannot find much documentation or examples on how to do this
What is the best way to create a real IncomingMessage stream, close it quickly and have a mocked get method? I think I would then need to mock a socket?
Or what is the correct way to test my class and function in this case?
Basically you need to mock your request object that can be done with below snippet:
function _mock_req() {
let req = {
get: jest.fn(() => 'hello!'),
};
return req;
}
Once mocking is done invoke it in your test case, here I'm using beforeEach that will take care of the issue: openHandler- Async callback was not invoked.
describe('http test', () => {
let req, res;
const myclass = new MyClass();
beforeEach((done) => {
req = _mock_req();
res = myclass.example(req);
done();
});
it('class test', () => {
expect(res).toEqual('hello!');
});
});
You also need to return request from your class to test get passed. While executing test case your mocked request object will get return from MyClass
export class MyClass {
example(request: IncomingMessage): void {
console.log(request.get('hello?'));
return request.get('hello?')
}
}
Test case result:
If you're not using express and using the built in http library for node you can do the following:
Note: This will also work with express too
import httpMocks from 'node-mocks-http';
import handleRequest from '../../../handlers/request';
describe('incoming message mock', () => {
it('working example', () => {
const mockRequest = httpMocks.createRequest({
method: 'POST',
url: '/my-fantastic-endpoint',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'content-length': '1',
'x-forwarded-for': '127.0.0.1',
},
});
mockRequest.destroy = jest.fn();
const mockResponse = httpMocks.createResponse();
handleRequest(mockRequest, mockResponse);
mockRequest.send({
example: 'hello!',
});
console.log(mockResponse._getData());
expect(true).toBe(true);
});
});

Import module with folder and passing data to module in nodejs

I Found The Tutorial about
Designing a clean REST API with Node.js (Express + Mongo)
project in github.
but the problem is i didn't get the concept of routing in one part.
the misundrestanding part is how is it possible to pass httpRequest data to handle method within contact-endpoint module?
because handle method is in here export default function makeContactsEndpointHandler({ contactList }) {
return async function handle(httpRequest) {
this is the index of project:
import handleContactsRequest from "./contacts";
import adaptRequest from "./helpers/adapt-request";
app.all("/contacts", contactsController);
app.get("/contacts/:id", contactsController);
function contactsController(req, res) {
const httpRequest = adaptRequest(req);
handleContactsRequest(httpRequest)
.then(({ headers, statusCode, data }) =>
res.set(headers).status(statusCode).send(data)
)
.catch((e) => res.status(500).end());
}
this is the adaptRequest:
export default function adaptRequest (req = {}) {
return Object.freeze({
path: req.path,
method: req.method,
pathParams: req.params,
queryParams: req.query,
body: req.body
})
}
this is the handleContactsRequest module:
import makeDb from "../db";
import makeContactList from "./contact-list";
import makeContactsEndpointHandler from "./contacts-endpoint";
const database = makeDb();
const contactList = makeContactList({ database });
const contactsEndpointHandler = makeContactsEndpointHandler({ contactList });
export default contactsEndpointHandler;
this is part of contact-endpoint module:
export default function makeContactsEndpointHandler({ contactList }) {
return async function handle(httpRequest) {
switch (httpRequest.method) {
case "POST":
return postContact(httpRequest);
case "GET":
return getContacts(httpRequest);
default:
return makeHttpError({
statusCode: 405,
errorMessage: `${httpRequest.method} method not allowed.`,
});
}
}
makeContactsEndpointHandler is a function that returns a function (async handle(xxx)).
In handleContactsRequest, we export the result of the call: makeContactsEndpointHandler({ contactList }). Which is therefore the function async handle(xxx) itself.
So, in index, when we call handleContactsRequest with the constant httpRequest as argument, we're actually calling that handle(xxx) function. (I wrote xxx as parameter name to highlight the difference between the two httpRequest declarations.)

Jest unit test to spy on lower-level method (NodeJS)

Trying to spy and override a function two levels down using Jest.
The test results say, "Expected mock function to have been called, but it was not called."
// mail/index.unit.test.js
import mail from './index';
import * as sib from '../sendinblue';
describe('EMAIL Util', () =>
test('should call sibSubmit in server/utils/sendinblue/index.js', async() => {
const sibMock = jest.spyOn(sib, 'sibSubmit');
sibMock.mockImplementation(() => 'Calling sibSubmit()');
const testMessage = {
sender: [{ email: 'foo#example.com', name: 'Something' }],
to: [{ email: 'foo#example.com', name: 'Something' }],
subject: 'My Subject',
htmlContent: 'This is test content'
};
await mail.send(testMessage);
expect(sibMock).toHaveBeenCalled();
})
);
mail.send() comes from here...
// mail/index.js
import { sibSendTransactionalEmail } from '../sendinblue';
export default {
send: async message => {
try {
return await sibSendTransactionalEmail(message);
} catch(err) {
console.error(err);
}
}
};
Which uses SendInBlue's API via axios (why I need to mock)...
// sendinblue/index.js
import axios from 'axios';
import config from '../../config/environment';
export async function sibSubmit(method, url, data) {
let instance = axios.create({
baseURL: 'https://api.sendinblue.com',
headers: { 'api-key': config.mail.apiKey }
});
try {
const response = await instance({
method,
url,
data
});
return response;
} catch(err) {
console.error('Error communicating with SendInBlue', instance, err);
}
}
export const sibSendTransactionalEmail = message => sibSubmit('POST', '/v3/smtp/email', message);
I assumed mail.send() would call sibSendTransactionalEmail() in the other module and it would call sibSubmit(), the focus of jest.spyOn(). Wondering where I went wrong.
jest.spyOn replaces the method on the object it is passed with a spy.
In this case you are passing sib which represents the ES6 module exports from sendinblue.js, so Jest will replace the module export for sibSubmit with the spy and give the spy the mock implementation you provided.
mail.send then calls sibSendTransactionalEmail which then calls sibSubmit directly.
In other words, your spy is not called because sibSendTransactionalEmail does not call the module export for sibSubmit, it is just calling sibSubmit directly.
An easy way to resolve this is to note that "ES6 modules support cyclic dependencies automatically" so you can simply import the module into itself and call sibSubmit from within sibSendTransactionalEmail using the module export:
import axios from 'axios';
import config from '../../config/environment';
import * as sib from './'; // import module into itself
export async function sibSubmit(method, url, data) {
let instance = axios.create({
baseURL: 'https://api.sendinblue.com',
headers: { 'api-key': config.mail.apiKey }
});
try {
const response = await instance({
method,
url,
data
});
return response;
} catch(err) {
console.error('Error communicating with SendInBlue', instance, err);
}
}
export const sibSendTransactionalEmail = message => sib.sibSubmit('POST', '/v3/smtp/email', message); // call sibSubmit using the module export
Note that replacing ES6 module exports with jest.spyOn like this works because Jest transpiles the ES6 modules to Node modules in a way that allows them to be mutated
Another way to work around this problem is to rewire the function you're spying on within the module, which is nicer since you don't have to modify the original code for the purposes of testing. You can use the rewire module if before ES6, or babel-rewire for ES6:
// mail/index.unit.test.js
import mail from './index';
import * as sib from '../sendinblue';
describe('EMAIL Util', () =>
test('should call sibSubmit in server/utils/sendinblue/index.js', async() => {
const sibMock = jest.spyOn(sib, 'sibSubmit');
sibMock.mockImplementation(() => 'Calling sibSubmit()');
//============ force the internal calls to use the mock also
sib.__set__("sibSubmit", sibMock);
//============
const testMessage = {
sender: [{ email: 'foo#example.com', name: 'Something' }],
to: [{ email: 'foo#example.com', name: 'Something' }],
subject: 'My Subject',
htmlContent: 'This is test content'
};
await mail.send(testMessage);
expect(sibMock).toHaveBeenCalled();
})
);

Typescript and Jest: Avoiding type errors on mocked functions

When wanting to mock external modules with Jest, we can use the jest.mock() method to auto-mock functions on a module.
We can then manipulate and interrogate the mocked functions on our mocked module as we wish.
For example, consider the following contrived example for mocking the axios module:
import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';
jest.mock('axios');
it('Calls the GET method as expected', async () => {
const expectedResult: string = 'result';
axios.get.mockReturnValueOnce({ data: expectedResult });
const result = await myModuleThatCallsAxios.makeGetRequest();
expect(axios.get).toHaveBeenCalled();
expect(result).toBe(expectedResult);
});
The above will run fine in Jest but will throw a Typescript error:
Property 'mockReturnValueOnce' does not exist on type '(url:
string, config?: AxiosRequestConfig | undefined) => AxiosPromise'.
The typedef for axios.get rightly doesn't include a mockReturnValueOnce property. We can force Typescript to treat axios.get as an Object literal by wrapping it as Object(axios.get), but:
What is the idiomatic way to mock functions while maintaining type safety?
Add this line of code const mockedAxios = axios as jest.Mocked<typeof axios>. And then use the mockedAxios to call the mockReturnValueOnce.
With your code, should be done like this:
import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
it('Calls the GET method as expected', async () => {
const expectedResult: string = 'result';
mockedAxios.get.mockReturnValueOnce({ data: expectedResult });
const result = await myModuleThatCallsAxios.makeGetRequest();
expect(mockedAxios.get).toHaveBeenCalled();
expect(result).toBe(expectedResult);
});
Please use the mocked function from ts-jest
The mocked test helper provides typings on your mocked modules and even their deep methods, based on the typing of its source. It makes use of the latest TypeScript feature, so you even have argument types completion in the IDE (as opposed to jest.MockInstance).
import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';
import { mocked } from 'ts-jest/utils'
jest.mock('axios');
// OPTION - 1
const mockedAxios = mocked(axios, true)
// your original `it` block
it('Calls the GET method as expected', async () => {
const expectedResult: string = 'result';
mockedAxios.mockReturnValueOnce({ data: expectedResult });
const result = await myModuleThatCallsAxios.makeGetRequest();
expect(mockedAxios.get).toHaveBeenCalled();
expect(result).toBe(expectedResult);
});
// OPTION - 2
// wrap axios in mocked at the place you use
it('Calls the GET method as expected', async () => {
const expectedResult: string = 'result';
mocked(axios).get.mockReturnValueOnce({ data: expectedResult });
const result = await myModuleThatCallsAxios.makeGetRequest();
// notice how axios is wrapped in `mocked` call
expect(mocked(axios).get).toHaveBeenCalled();
expect(result).toBe(expectedResult);
});
I can't emphasise how great mocked is, no more type-casting ever.
To idiomatically mock the function while maintaining type safety use spyOn in combination with mockReturnValueOnce:
import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';
it('Calls the GET method as expected', async () => {
const expectedResult: string = 'result';
// set up mock for axios.get
const mock = jest.spyOn(axios, 'get');
mock.mockReturnValueOnce({ data: expectedResult });
const result = await myModuleThatCallsAxios.makeGetRequest();
expect(mock).toHaveBeenCalled();
expect(result).toBe(expectedResult);
// restore axios.get
mock.mockRestore();
});
A usual approach to provide new functionality to imports to extend original module like declare module "axios" { ... }. It's not the best choice here because this should be done for entire module, while mocks may be available in one test and be unavailable in another.
In this case a type-safe approach is to assert types where needed:
(axios.get as jest.Mock).mockReturnValueOnce({ data: expectedResult });
...
expect(axios.get as jest.Mock).toHaveBeenCalled();
#hutabalian The code works really well when you use axios.get or axios.post but if you use a config for requests the following code:
const expectedResult: string = 'result';
const mockedAxios = axios as jest.Mocked<typeof axios>;
mockedAxios.mockReturnValueOnce({ data: expectedResult });
Will result in this error:
TS2339 (TS) Property 'mockReturnValueOnce' does not exist on type
'Mocked'.
You can solve it like this instead:
AxiosRequest.test.tsx
import axios from 'axios';
import { MediaByIdentifier } from '../api/mediaController';
jest.mock('axios', () => jest.fn());
test('Test AxiosRequest',async () => {
const mRes = { status: 200, data: 'fake data' };
(axios as unknown as jest.Mock).mockResolvedValueOnce(mRes);
const mock = await MediaByIdentifier('Test');
expect(mock).toEqual(mRes);
expect(axios).toHaveBeenCalledTimes(1);
});
mediaController.ts:
import { sendRequest } from './request'
import { AxiosPromise } from 'axios'
import { MediaDto } from './../model/typegen/mediaDto';
const path = '/api/media/'
export const MediaByIdentifier = (identifier: string): AxiosPromise<MediaDto> => {
return sendRequest(path + 'MediaByIdentifier?identifier=' + identifier, 'get');
}
request.ts:
import axios, { AxiosPromise, AxiosRequestConfig, Method } from 'axios';
const getConfig = (url: string, method: Method, params?: any, data?: any) => {
const config: AxiosRequestConfig = {
url: url,
method: method,
responseType: 'json',
params: params,
data: data,
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' },
}
return config;
}
export const sendRequest = (url: string, method: Method, params?: any, data?: any): AxiosPromise<any> => {
return axios(getConfig(url, method, params, data))
}
Starting with ts-jest 27.0 mocked from ts-jest will be deprecated and removed in 28.0 you can check it in the official documentation. So please use instead jest.mocked from jest. Here's the documentation
So for your example:
import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';
jest.mock('axios');
// OPTION - 1
const mockedAxios = jest.mocked(axios, true)
// your original `it` block
it('Calls the GET method as expected', async () => {
const expectedResult: string = 'result';
mockedAxios.mockReturnValueOnce({ data: expectedResult });
const result = await myModuleThatCallsAxios.makeGetRequest();
expect(mockedAxios.get).toHaveBeenCalled();
expect(result).toBe(expectedResult);
});
After updating to the newest Axios (0.21.1) I started to have this kind of problem. I tried a lot of solutions but with no result.
My workaround:
type axiosTestResponse = (T: unknown) => Promise<typeof T>;
...
it('some example', async () => {
const axiosObject = {
data: { items: [] },
status: 200,
statusText: 'ok',
headers: '',
config: {},
} as AxiosResponse;
(Axios.get as axiosTestResponse) = () => Promise.resolve(axiosObject);
});

Resources