jest mock functions but keep some original code for others? - javascript

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();
});
});

Related

issue with mocking a method which makes database calls using jest

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();
});

Function not getting called in useEffect()

I want these two functions to be called every time the component renders, but they are not being executed. And when I put the functions in the dependency array it results in an infinite loop. Any idea why they are not being called?
function PortfolioComponent() {
const [requestedAssets, setRequestedAssets] = useState([]);
const [assets, setAssets] = useState([]);
useEffect(() => {
async function calcValue() {
Promise.all(
requestedAssets.map(async function (asset) {
try {
const response = await axios.get(assetData(asset.AssetId));
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.Amount);
return { ...asset, value: value, price: cp };
} catch (error) {
console.log(error.response.data.error);
throw error;
}
})
)
.then((newAssetArray) => {
setAssets(newAssetArray);
console.log(newAssetArray);
console.log(assets);
})
.catch((error) => {
console.log(error);
});
}
async function getAssets() {
try {
const response = await axios.get("http://localhost:4200/assets");
// Do as you wish with response here
const assetResponse = response.data.rows;
setRequestedAssets(assetResponse);
console.log(requestedAssets);
} catch (error) {
console.log(error.response.data.error);
}
}
getAssets();
calcValue();
}, []);
Also some weird behaviour I just discovered...
For example, this line of code:
let cp = await response.data.market_data.current_price.eur;
When I remove the await keyword and save it in VS code, the data is retrieved as expected. However, when I refresh the browser the arrays are empty again. The same goes for when I add the await keyword again and save. The same thing happens.
This is what worked for me. So, instead of having a useState variable for requestedAssets, I created a variable inside the getAssets method instead. I'm not exactly sure why this works and not the other way. But, if anybody could explain, that would be great.
function PortfolioComponent() {
//const [requestedAssets, setRequestedAssets] = useState([]);
const [assets, setAssets] = useState([]);
useEffect(() => {
async function getAssets() {
const response = await axios.get("http://localhost:4200/assets");
const requestedAssets = response.data.rows;
console.log(requestedAssets);
Promise.all(
requestedAssets.map(async function (asset) {
try {
const response = await axios.get(assetData(asset.AssetId));
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.Amount);
return { ...asset, value: value, price: cp };
} catch (error) {
console.log(error.response.data.error);
throw error;
}
})
)
.then((newAssetArray) => {
setAssets(newAssetArray);
console.log(newAssetArray);
console.log(assets);
})
.catch((error) => {
console.log(error);
});
}
getAssets();
}, []);
The recommendation is to declare your functions inside the useEffect, see the official documentation. If you keep scrolling in the docs, they even have an example similar to yours, with an async function.
If, for some reason, you do need to have your function declared outside the useEffect, you can use a useCallback, which allows you to declare them in the dependency array. Something like this:
const getAssets = useCallback(async() => {
try {
const response = await axios.get("http://localhost:4200/assets");
// Do as you wish with response here
const assetResponse = response.data.rows;
setRequestedAssets(assetResponse);
console.log(requestedAssets);
} catch (error) {
console.log(error.response.data.error);
}
}, [requestedAssets])
useEffect(() => {
getAssets()
}, [getAssets])
You can also see the section Do I need to specify functions as effect dependencies or not? in this blog here for more information.
PS: This blog is from Dan Abramov, one of the creators of React, so reliable source ;)

How do I setup this JS code to do better testing?

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();
}

Jest mocking a module

I'm trying to mock a module import using Jest and I'm struggling for some reason. I've got the following code:
src/elastic.js
const getRolesFunc = elasticClient => async username => {
// Do some stuff
}
module.exports = { getRolesFunc };
src/handlerFactory.js
const { getRolesFunc } = require("../src/elastic");
const handlerFactory = elasticClient =>
async (event) => {
const getRolesAsync = getRolesFunc(elasticClient);
const roles = await getRolesAsync();
}
}
My test file currently looks like:
tests/handlerFactory.unit.test.js
const { handlerFactory } = require("../src/handlerFactory");
const { getRolesFunc } = require("../src/elastic");
jest.mock("../src/elastic", () => ({
getRolesFunc: jest.fn(),
}));
describe("handlerFactory", () => {
it("handler returns correct response", async () => {
getRolesFunc.mockImplementation(() => "foo");
// Call the handler to get our actual result
const handlerAsync = handlerFactory({});
const result = await handlerAsync(event);
});
});
At the moment however I'm getting an error in my test:
TypeError: getRolesFunc.mockImplementation is not a function
I've tried a few things none of which worked, this feels like the closest but I can't work out why the jest.mock isn't working correctly. I've looked at a few examples and still can't work out why this I can't get mocking working. Can anyone help point out what I've done wrong?
As you have module.exports = { getRolesFunc }; you need to below change in your code:
const { handlerFactory } = require("../src/handlerFactory");
const elasticObj = require("../src/elastic");
jest.mock("..src/elastic");
// in your example, now put below code:
elasticObj.getRolesFunc.mockImplementation(() => "foo");

Locked it method in chai

I have a js file which supplies some db operations. This file works with promises only which can be chained. To test that class I work with an async function.
The problem is, that whenever I work with promises inside my test function the it function gets blocked for every other test later.
Here are two examples:
'use strict'
const exec = require('child_process').exec
const path = require('path')
const request = require('request')
const expect = require('chai').expect
const createTableStatements = require('../data')
test()
async function test () {
await testGetUser()
console.log('1')
await testGetFaculties()
}
function testGetUser () {
return new Promise((resolve1) => {
describe('test get user', function () {
const db = require('../dbInterface')
it('test get user should be complete', function () {
db.dbFunctions.dropAll()
.then(onResolve => {
return db.dbFunctions.createTable(createTableStatements.createTableStatements.user)
}
)
.then(() => {
console.log('success create user table')
return db.dbFunctions.addUser('1', 'firstName', 'lastName', 'email')
})
.then(resolve => {
return db.dbFunctions.getUser('email', undefined)
})
.then(result => {
expect(result.toString().includes('dummy')).to.equal(false)
})
.then(resolve => {
return db.dbFunctions.dropAll()
})
.then(resolve => {
console.log('resolve')
resolve1()
})
.catch(err => console.log(err))
})
})
})
}
function testGetFaculties () {
return new Promise(resolve => {
describe('test get faculties', function () {
let db
before(function () {
db = require('../dbInterface')
})
console.log('displayed')
it('should work', function () {
console.log('locked')
expect(db.dbFunctions.getFaculties('hsa')).to.be.an('array').that.does.include('Science')
resolve()
})
})
})
}
And this is the output
resolve
1
displayed
As you can see console.log('locked') is not being processed.
What i figured out so far, that I only have this issue when I call expect within a then function. But this is necessary for my tests.
The test () function should contain much more tests, only for this question I shortened it.
And for clarification: If I only test methods type of testGetFaculties () which don't contains another promise chain inside it works like it should.
Any idea why this is like it is?
Most probably the console.log( 'locked' ); doesn't do anything, because your previous test case was not finished at all.
Writing describe, it, before inside a Promise and containing unreturned Promises is something that you should not do.
Much better test case would look like :
'use strict'
const exec = require('child_process').exec
const path = require('path')
const request = require('request')
const expect = require('chai').expect
const createTableStatements = require('../data')
// You use this in both test cases anyway
const db = require('../dbInterface');
describe('test get user', function () {
it('test get user should be complete', function () {
return db
// ^ returning promise will make sure that the test ends when the promise ends.
.dbFunctions
.dropAll()
.then(onResolve => { ... } )
...
)
} );
} );
describe('test get faculties', function () {
it('should work', function () {
return db
// ^ returning promise will make sure that the test ends when the promise ends.
.dbFunctions
.getFaculties('hsa')
.then( value => {
// ^ You actually need to test the value of the resolve promise
expect( value ).to.be.an('array').that.does.include('Science');
} )
} );
} );

Categories