I am facing issues mocking a method which internally makes call to database to fetch data. Please find the code below:
const getData = async (...args) => {
// first call
await db.getData();
// second call
await db.queryData();
return data;
}
const generateResult = async (...args) => {
const data = await getData(...args);
// rest of the code
return result;
}
export ClassTest;
In the test file:
describe(('Verify generateResult') => {
jest.spyOn(generateResult, 'getData').mockImplementation((...args) => {
return data;
});
it('should return result', async () => {
const result = await generateResult(..args);
expect(result).toBeTruthy();
});
});
The getData method makes database calls to retrieve data. Rest of the code just massages the data and returns result. Even though its been mocked the code fails when the database calls are made. I assume that it should get data from mocked implementation and rest of the code should execute. Not sure what is wrong here. Could anyone pls let me know. Do not have a lot of experience writing jest test cases.
thanks
Any chance that you could move the getData method to another module? Then you can mock the getData and the imported version would be the mocked one everywhere. Just an idea.
getData.js
export const getData = async (...args) => {
const data = await Promise.resolve(false);
console.log('original called')
return data;
}
dbUtils.js
import { getData } from './getData'
export const generateResult = async (...args) => {
const data = await getData(...args);
return data;
}
and the test:
import * as getDataUtils from '../getData';
import { generateResult } from '../dbUtils';
it('should return result', async () => {
jest.spyOn(getDataUtils, 'getData').mockResolvedValue(true);
const result = await generateResult();
expect(result).toBeTruthy();
});
Related
Scenario is, I have a function that needs to be unit tested: AppleLoginService().
It will contain 3 other functions within it. These need to be mocked. Simple.
//UserService.js
const AppleLoginService = async () => {
await verifyAppleToken();
const response = await verifyAuthorizationCode(authorizationCode);
const id = await RegisterIdpAccount(); //Can ignore for now.
return {id}
}
async function verifyAppleToken() {
//etc code
}
async function verifyAuthorizationCode() {
//etc code
}
The issue is, I need to retain AppleLoginService original code but mock everything else but cannot do so.
I've tried:
But obviously, mocks AppleLoginService which isn't what I want.
jest.mock("UserService");
The functions doesn't actually get mocked in this way which is weird. I've put logs in the functions and ends up printing.
jest.mock("UserService", () => {
const original = jest.requireActual("UserService");
return {
...original,
verifyAppleToken: jest.fn(),
verifyAuthorizationCode: jest.fn()
}
});
Also doesn't get mocked. Original implementation runs.
const verifyToken = jest.spyOn(services, "verifyAppleToken").mockImplementation(() => true);
const verifyCode = jest.spyOn(services, "verifyAuthorizationCode").mockImplementation(() => 1);
const services = require("UserService.js");
const queries = require("SQLTransactionService.js"));
/*
jest.mock() ETC
*/
describe("When using AppleLoginService,", () => {
it("given all values are correct then, return response", async () => {
services.verifyAppleToken.mockReturnValueOnce(true);
services.verifyAuthorizationCode.mockResolvedValueOnce("ANYTHING");
queries.RegisterIdpAccount.mockResolvedValueOnce(1);
const res = await services.AppleLoginService("apple", signUpPayload);
expect(services.verifyAppleToken).toBeCalledTimes(1); // Is called 0 times
expect(services.verifyAuthorizationCode).toBeCalledTimes(1); // Is called 0 times.
expect(res).toBeDefined();
});
});
In my react app, I am currently passing a list of stores by calling the API directly from the URL.
const getStore = async () => {
try {
const response = axios.get(
'http://localhost:3001/appointment-setup/storeList'
);
return response;
} catch (err) {
console.error(err);
return false;
}
};
I pass this function into my useEffect hook where I would set my get a list of stores using resp.data.stores:
const [storeLocations, setStoreLocations] = useState([]);
useEffect(() => {
async function getData(data) {
await service.stepLocation.init();
const resp = await getStore();
setStoreLocations(resp.data.stores);
}
setFlagRender(true);
return getData();
}, []);
This works, however, I noted in useEffect there is a call await service.stepLocation.init(). There is a file that already takes care of all the backend/data for the component.
const stepLocation = {
// removed code
// method to retrieve store list
retrieveStoreList: async function ()
let response = await axios.get(
constants.baseUrl + '/appointment-setup/storeList'
);
return response.data.stores;
,
// removed code
Since this data is available, I don't need the getStore function. However when I try to replace response.data.stores in useEffect with service.stepLocation.retrieveStoreList no data is returned. How do I correctly pass the data from this file in my useEffect hook?
I think your useEffect should be like follows as you want to save the stores in your state.
useEffect(() => {
const updateStoreLocations = async () => {
const storeLocations = await service.stepLocation.retrieveStoreList();
setStoreLocations(storeLocations);
}
updateStoreLocations();
}, [])
Hi guys I'm having trouble testing the below JS using Jest. It starts with waitForWorker. if the response is 'working' then it calls waitForWorker() again. I tried Jest testing but I don't know how to test an inner function call and I've been researching and failing.
const $ = require('jquery')
const axios = require('axios')
let workerComplete = () => {
window.location.reload()
}
async function checkWorkerStatus() {
const worker_id = $(".worker-waiter").data('worker-id')
const response = await axios.get(`/v1/workers/${worker_id}`)
return response.data
}
function waitForWorker() {
if (!$('.worker-waiter').length) {
return
}
checkWorkerStatus().then(data => {
// delay next action by 1 second e.g. calling api again
return new Promise(resolve => setTimeout(() => resolve(data), 1000));
}).then(worker_response => {
const working_statuses = ['queued', 'working']
if (worker_response && working_statuses.includes(worker_response.status)) {
waitForWorker()
} else {
workerComplete()
}
})
}
export {
waitForWorker,
checkWorkerStatus,
workerComplete
}
if (process.env.NODE_ENV !== 'test') $(waitForWorker)
Some of my test is below since i can't double check with anyone. I don't know if calling await Worker.checkWorkerStatus() twice in the tests is the best way since waitForWorker should call it again if the response data.status is 'working'
import axios from 'axios'
import * as Worker from 'worker_waiter'
jest.mock('axios')
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: { reload: jest.fn() }
})
});
beforeEach(() => jest.resetAllMocks() )
afterEach(() => {
jest.restoreAllMocks();
});
describe('worker is complete after 2 API calls a', () => {
const worker_id = Math.random().toString(36).slice(-5) // random string
beforeEach(() => {
axios.get
.mockResolvedValueOnce({ data: { status: 'working' } })
.mockResolvedValueOnce({ data: { status: 'complete' } })
jest.spyOn(Worker, 'waitForWorker')
jest.spyOn(Worker, 'checkWorkerStatus')
document.body.innerHTML = `<div class="worker-waiter" data-worker-id="${worker_id}"></div>`
})
it('polls the correct endpoint twice a', async() => {
const endpoint = `/v1/workers/${worker_id}`
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint]])
expect(data).toMatchObject({"status": "working"})
})
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint],[endpoint]])
expect(data).toMatchObject({"status": "complete"})
})
})
it('polls the correct endpoint twice b', async() => {
jest.mock('waitForWorker', () => {
expect(Worker.checkWorkerStatus).toBeCalled()
})
expect(Worker.waitForWorker).toHaveBeenCalledTimes(2)
await Worker.waitForWorker()
})
I think there are a couple things you can do here.
Inject status handlers
You could make the waitForWorker dependencies and side effects more explicit by injecting them into the function this lets you fully black box the system under test and assert the proper injected effects are triggered. This is known as dependency injection.
function waitForWorker(onComplete, onBusy) {
// instead of calling waitForWorker call onBusy.
// instead of calling workerComplete call onComplete.
}
Now to test, you really just need to create mock functions.
const onComplete = jest.fn();
const onBusy = jest.fn();
And assert that those are being called in the way you expect. This function is also async so you need to make sure your jest test is aware of the completion. I notice you are using async in your test, but your current function doesnt return a pending promise so the test will complete synchronously.
Return a promise
You could just return a promise and test for its competition. Right now the promise you have is not exposed outside of waitForWorker.
async function waitForWorker() {
let result = { status: 'empty' };
if (!$('.worker-waiter').length) {
return result;
}
try {
const working_statuses = ['queued', 'working'];
const data = await checkWorkerStatus();
if (data && working_statuses.includes(data.status)) {
await waitForWorker();
} else {
result = { status: 'complete' };
}
} catch (e) {
result = { status: 'error' };
}
return result;
}
The above example converts your function to async for readability and removes side effects. I returned an async result with a status, this is usefull since there are many branches that waitForWorker can complete. This will tell you that given your axios setup that the promise will complete eventually with some status. You can then use coverage reports to make sure the branches you care about were executed without worrying about testing inner implementation details.
If you do want to test inner implementation details, you may want to incorporate some of the injection principals I mentioned above.
async function waitForWorker(request) {
// ...
try {
const working_statuses = ['queued', 'working'];
const data = await request();
} catch (e) {
// ...
}
// ...
}
You can then inject any function into this, even a mock and make sure its called the way you want without having to mock up axios. In your application you simply just inject checkWorkerStatus.
const result = await waitForWorker(checkWorkerStatus);
if (result.status === 'complete') {
workerComplete();
}
In the below function I have to mock httpGet function, so instead of call actual function and returning the value it should call a mock function
getStudents: async(req,classId) => {
let result = await httpGet(req);
return result;
},
My test case
describe('Mock',()=>{
it('mocking api',async()=>{
const result = await getStudents(req,classId);;
console.log(result);
})
})
you can have the same jest.fn() as a mock for the function as you would do for a normal one.
then you can either implement your own Promise return value, or use jests's mockResolvedValue or mockRejectedValue
https://jestjs.io/docs/en/mock-function-api#mockfnmockresolvedvaluevalue
For example:
import { httpGet } from 'http';
jest.mock('http'); // this is where you import the httpGet method from
describe('Mock',()=>{
it('mocking api',async() => {
httpGet.mockResolvedValue(mockresult); // httpGet should already be a jest.fn since you used jest.mock
const result = await getStudents(req,classId);
console.log(result);
})
})
I would mock this as an ES6 module. In your tests, stick this at the top of your file
jest.mock('http', () => {
const originalModule = jest.requireActual('http')
return {
__esModule: true, // necessary to tag this as an ES6 Module
...originalModule, // to bring in all the methods
httpGet: jest.fn().mockResolvedValue({ /* the object you want returned */ })
}
})
I'm desperate for help here. I'm trying to test a function with jest but I'm stuck on one thing. When I try to mock the fetch request I get this error:
TypeError: Cannot read property 'json' of undefined
The function I'm trying to test is this:
const updateUI = async () =>{
const res = await fetch('/sentiment');
console.log(res);
try {
console.log(res.data)
const allData = await res.json();
console.log(allData)
document.getElementById("polarity").innerHTML = allData.polarity;
document.getElementById("polarityConfidence").innerHTML = allData.polarity_confidence;
document.getElementById("subjectivity").innerHTML = allData.polarity;
document.getElementById("subjectivityConfidence").innerHTML = allData.polarity_confidence;
return allData;
}catch(error){
console.log('error')
}
};
export { updateUI }
The test I'm trying to run is this:
import "regenerator-runtime/runtime";
import "core-js/stable";
import "fetch-mock"
const fetchMock = require('fetch-mock');
fetchMock.config.sendAsJson = true; \\I've tried with and without this part and I get the same error
import updateUI from './updateUI';
import { isIterable } from "core-js";
describe('updateUI', () => {
it('can fetch', async () => {
fetchMock.get('/sentiment', {polarity: 'polarity', polarity_confidence: 'polarity confidence', subjectivity: 'subjectivity', subjectivity_confidence: 'subjectivity confidence'});
const res = await updateUI('/sentiment');
const allData = await res.json();
expect(allData.polarity).toEqual('polarity');
expect(allData.polarity_confidence).toEqual('polarity confidence');
expect(allData.subjectivity).toEqual('subjectivity');
expect(allData.subjectivity_confidence).toEqual('subjectivity confidence');
})
})
I really have no idea where to go from here. Why won't it get the json object? Is it because my updateUI function calls the json object in the try{} part of the function? If that is the case how do I test it?
I see two problems here
In your test you are passing string like this const res = await updateUI('/sentiment'); which wouldn't matter as updateUI doesn't take any parameter.
In the next line you are doing res.json() which wouldn't work as from your actual method you are only returning response. You in your test you don't need to do .json(). This is the reason you are getting undefined as there is no json function.
This is how I did it. More info can be found on codegrepper
Make sure the Promise.resolve is used well and the mocked and api data both has the same format.
const mockFetchUserData = (data) => {
return global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => data
})
)
}
it('Display empty list of users', async () => {
// Data can be empty array or array of data
await mockFetchUserData([])
await act(async () =>
render(
<Router>
<User />
</Router>
)
})
const findSearchButtonText = screen.getByText('Search') // is search button rendered
expect(findSearchButtonText.type).toBe('submit') // is type submit
}