Unit Testing redux async function with Jest - javascript

I'm pretty new to unit testing so please pardon any noobness.
I have a file api.js which has all the API call functions for the app. Each function returns its promise. Here's how it looks:
api.js
const api = {
getData() {
return superagent
.get(apiUrl)
.query({
page: 1,
});
},
}
Now coming to the redux async action that i'm trying to test. It looks something like this:
getDataAction.js
export function getData(){
return dispatch => {
api.getData()
.end((err, data) => {
if (err === null && data !== undefined) {
console.log(data);
} else if (typeof err.status !== 'undefined') {
throw new Error(`${err.status} Server response failed.`);
}
});
}
}
Now, In my test file, I've tried this:
getDataAction.test.js
jest.mock('api.js');
describe('getData Action', () => {
it('gets the data', () => {
expect(store.dispatch(getData())).toEqual(expectedAction);
});
});
This, throws me an error:
TypeError: Cannot read property 'end' of undefined
What am I doing wrong ? Now i'm able to mock api.js with default automocker of Jest, but how do I handle the case of running callback function with end ? Thanks a lot for any help !

Your mock of api needs to return a function that returns an object that has the end function:
import api from 'api' //to set the implantation of getData we need to import the api into the test
// this will turn your api into an object with the getData function
// initial this is just a dumb spy but you can overwrite its behaviour in the test later on
jest.mock('api.js', ()=> ({getData: jest.fn()}));
describe('getData Action', () => {
it('gets the data', () => {
const result = {test: 1234}
// for the success case you mock getData so that it returns the end function that calls the callback without an error and some data
api.getData.mockImplementation(() => ({end: cb => cb(null, result)}))
expect(store.dispatch(getData())).toEqual(expectedAction);
});
it('it thows on error', () => {
// for the error case you mock getData so that it returns the end function that calls the callback with an error and no data
api.getData.mockImplementation(() => ({end: cb => cb({status: 'someError'}, null)}))
expect(store.dispatch(getData())).toThrow();
});
});

Related

How to Inject multiple mock promises to unit test the fetch in JS?

I'm trying to setup unit tests for the api calls in my JS project.
Able to set it up for a single API call, using the following format
describe('Token refresh success', () => {
beforeAll(() => {
global.fetch = () =>
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshSuccess', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('SYoHdm4yw');
expect(tokenData.refresh_token).toEqual('QxJ3yEgX4NThbTE66u7lshWTpQkRBilq');
});
})
Now this format works great, and I can create individual tests by injecting one promise.
Now, there is a case I want to unit test where a particular API call is made twice. I need to inject fail response the first time, and success response the second time.
I tried the following approach, but did not work:
describe('Token refresh trigger as expected on create token fail', () => {
beforeAll(() => {
global.fetch = () => [
Promise.resolve({
json: () => Promise.reject(mockError(400)),
}),
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
]
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshTriggerOnTokenCreateFail', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('jkhjk');
expect(tokenData.refresh_token).toEqual('dfdfdf');
});
})
and my tokenRefresh() function is supposed to get called 2 times if it gets 400 error.
async tokenRefresh(retryCount = 0) {
console.log('tokenRefresh');
const request = this.getTokenRefreshRequest();
try {
const response = await this.fetchData(request);
console.log('token refresh success', response);
return { access_token, refresh_token };
} catch (err) {
if ((err.status == 400 || err.status == 401) && retryCount < 2) {
await this.tokenRefresh(retryCount++);
} else {
throw new Error(`unable to refresh a token from API ${err.status}`);
}
}
};
I'm able to verify that by using single promise
Promise.resolve({
json: () => Promise.reject(mockError(400)),
})
the tokenRefresh() gets called again as expected, but in the second time I could not figure out how to pass the success promise, at the moment it fails the second time too.
I was able to find a working combination
beforeAll(() => {
global.fetch = fetchMock.mockRejectOnce(customError) // makes sure first api call fails
.mockResponseOnce(JSON.stringify(successResponse)) // makes sure second api call succeeds
})
This made my test pass

How to mock s3 putObject with promise function with jest

I see the answer here Mocking aws-sdk S3#putObject instance method using jest, but it's not working as-is and it's not explained at all so I don't know what's going on.
I have a function:
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
exports.saveImageToS3 = async (params) => {
try {
const s3resp = await s3.putObject(params).promise();
/* Happy path response looks like this:
data = {
ETag: "\"6805f2cfc46c0f04559748bb039d69ae\"",
VersionId: "psM2sYY4.o1501dSx8wMvnkOzSBB.V4a" // version optional
}*/
if (s3resp.hasOwnProperty('ETag')) { // expected successful response
return { success: true, key: params.Key } // returning key of item if we have reason to think this was successful.
} else {
console.warn("Unexpected s3 response format: ", s3resp)
return s3resp;
}
} catch (err) {
throw err;
}
}
I'd like to test if it works.
I do not understand how to mock the s3.putObject function.
I have tried:
describe('FUNCTION: saveImageToS3', () => {
const mockedPutObject = jest.fn();
jest.mock('aws-sdk', () => {
return class S3 {
putObject(params, cb) {
console.log("Mocked putObject function");
mockedPutObject(params, cb);
}
}
});
test('returns success object with correct key value', async () => {
await expect(await utils.saveImageToS3(params)).toEqual({ success: true, key: `original/testCamId/testCamId___2022-04-06T06-30-59Z.jpg` })
})
})
per the above-linked answer, but the test fails (times out, actually) and the output "Mocked putObject function" never is written to the console, telling me the mocked aws-sdk isn't being used...

How to test a function that returns fetch promise

I have never had to test a function like this before. Any ideas would be appreciated. The function basically passes a url to fetch and returns json. The url tells fetch which static json file to get.
const getJSON = async <T>(url): Promise<T> => {
try {
return await fetch(url).then(res => {
if (!res.ok) {
throw new Error('Network response was not ok.');
}
if (!res.headers.get('content-type').includes('application/json')) {
throw new Error('Response is not JSON');
}
return res.json();
});
} catch (e) {
// some error handling
}
};
export default fetchJSON;
At first glance I don't even know what to test. The only thing I came up with was to try and spy on the fetch method and force the response to be a some mocked data. I tried the following. I tried to mock fetch and call it. The expect seen below passes but the result variable is undefined so I can't test any return value.
import fetchJSON from '../fetchJSON';
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: 'foo' }),
})
);
beforeEach(() => {
fetch.mockClear();
});
it('finds blah blah', async () => {
const result = await fetchJSON('');
expect(fetch).toHaveBeenCalledTimes(1);
});

How to mock function, that is called in "then" in axios promise?

I have a function, that is fetching data from the backend. When fetch is successful, it extracts one value from the response, and then call another function (parseAllRecordsData), that is converting the value into other value. I'm trying to test this function, but after mocking parseAllRecordsData function, it's still trying to call original function (and throws errors from that function).
In other tests jest.fn or jest.spy is working correctly, but when I'm trying to mock function that is used in "then" it's not.
export function fetchAllRecordsData(payload) {
const url = `/apis/${payload.link.split('apis/')[1]}`;
return axios.get(url)
.then(({ data }) => {
if (data && data._embedded) {
const parsedData = data._embedded['taxonomies:entry'];
const arrayData = parseAllRecordsData(parsedData, payload);
return { data: List(arrayData) };
}
return { data: List([]) };
})
.catch((error) => ({ error }));
}
And my test:
describe('fetchAllRecordsData', () => {
const mockedPayload = {
link: 'apis/ok_link',
};
beforeAll(() => {
jest.spyOn(LegalListRecordsApi,'parseAllRecordsData').mockReturnValue(['test']);
});
it('test', async () => {
const test = await LegalListRecordsApi.fetchAllRecordsData(mockedPayload);
expect(test).toEqual(1);
});
});
When it's called like this, parseAllRecordsData calls real function, and throws the error, because mocked Axios response doesn't have some values that parsing function use. I'm only interested in return value, not calling this function.
jest.spyOn(LegalListRecordsApi,'parseAllRecordsData').mockReturnValue(['test']); mocks the module export for parseAllRecordsData.
This doesn't have any effect on fetchAllRecordsData because it is in the same module as parseAllRecordsData and is calling it directly.
ES6 modules supports cyclic dependencies so you can import a module into itself.
Import the module into itself and use the module to call parseAllRecordsData:
import * as LegalListRecordsApi from './LegalListRecordsApi'; // import module into itself
export function fetchAllRecordsData(payload) {
const url = `/apis/${payload.link.split('apis/')[1]}`;
return axios.get(url)
.then(({ data }) => {
if (data && data._embedded) {
const parsedData = data._embedded['taxonomies:entry'];
const arrayData = LegalListRecordsApi.parseAllRecordsData(parsedData, payload); // use the module
return { data: List(arrayData) };
}
return { data: List([]) };
})
.catch((error) => ({ error }));
}
...and the call will be mocked when you mock the module export for parseAllRecordsData.
export function fetchAllRecordsData(payload, axiosInterface = axios) {
return return axiosInterface.get(url)
. then(({ data }) => {
// your code
})
.catch((error) => ({ error }))
}
So, you need create mock object with method get, method get should return promise.

Properly stubbing request/response with Sinon/Mocha

I'm relatively new to unit-testing on the backend and need some guidance as to how to unit-test the following. I'm using Mocha/Should/Sinon.
exports.get = function(req, res) {
if (req.query.example) {
return res.status(200).json({ success: true });
} else {
return res.status(400).json({error: true});
}
}
You can use Sinon's spy and stub functions to test your code like this:
const { spy, stub } = require('sinon');
const chai = require('chai');
chai.should();
describe('the get function', () => {
let status,
json,
res;
beforeEach(() => {
status = stub();
json = spy();
res = { json, status };
status.returns(res);
});
describe('if called with a request that has an example query', () => {
beforeEach(() => get({ query: { example: true } }, res));
it('calls status with code 200', () =>
status.calledWith(200).should.be.ok
);
it('calls json with success: true', () =>
json.calledWith({ success: true }).should.be.ok
);
});
describe('if called with a request that doesn\'t have an example query', () => {
beforeEach(() => get({ query: {} }, res));
it('calls status with code 400', () =>
status.calledWith(400).should.be.ok
);
it('calls json with error: true', () =>
json.calledWith({ error: true }).should.be.ok
);
});
});
In the first beforeEach call, I'm creating a stub named status and a spy named json.
The status stub is used to test if the status method of the response is called with the correct response code.
The json spy is used to test if the json method of the response is called with the correct JSON code.
Note I'm using stub for status to be able to return the mock response from any call that goes to the status method, otherwise the chaining (res.status().json()) would not work.
For json it suffices to use a simple spy, because it is at the end of the chain.

Categories

Resources