Jest: How to mock function and test that it was called? - javascript

I have a method that loops through an array and makes a HTTP request for each element:
file-processor.js
const chunkFile = (array) => {
return array.map(el => {
sendChunkToNode(el);
});
};
const sendChunkToNode = (data) =>
new Promise((resolve, reject) => {
request.post(...);
});
export default {
chunkFile,
sendChunkToNode,
};
I already have a test for sendChunkToNode:
describe("sendChunkToNode", () => {
beforeEach(() => {
// TODO: figure out how to mock request with certain params
nock(API.HOST)
.post(API.V1_UPLOAD_CHUNKS_PATH)
.reply(201, {
ok: true
});
});
it("makes a POST request to /api/v1/upload-chunks", async () => {
const response = await FileProcessor.sendChunkToNode(
1,
"not_encrypted",
"handle"
);
expect(response).toEqual({ ok: true });
});
});
I now want a test for chunkFile. In the test I just want to test that sendChunkToNode was called with the different elements. Something like this:
describe("chunkFile", () => {
it("calls sendChunkToNode with each element", async () => {
expect(sendChunkToNode).toBeCalledWith(1) //<--- I made the toBeCalledWith method up
expect(sendChunkToNode).toBeCalledWith(2) //<--- I made the toBeCalledWith method up
chunkFile([1,2]);
});
});
Is there a way to do this in Jest?

The way you have it written, you should just make a loop and execute nock for as many times as the number of elements in the array. Then, you call chunkFile(); if there are pending nock requests, that means something is wrong and the test will fail.
test('...', () => {
const someArray = ['a', 'b', 'c']; // someArray.length == 3
someArray.forEach(element => {
nock(...); // set up nock using element value
});
// if the nock interceptors you set up don't match any request, it will throw an error
chunkFile(someArray);
});

Related

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

testing variable within the function in class using mocha

I need to test value of the url variable in the pullPackage() function in the TASK class.
class TASK {
constructor(taskData, done) {
//some code
}
// Generic Setup
pullPackage() {
return new Promise((resolve, reject) => {
fs.emptydir(this.taskDir, (err) => {
if (err) return reject(err);
const git = require('simple-git')(this.taskDir);
let url = '';
console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV === 'test') {
// url = 'ssh://testuser#127.0.0.1:4000/testuser/test-repo-1.git'; // make this match the below format
url = '/git/testuser/test-repo-1';
} else {
const gitAddress = new URL(config.config.GIT_ADDRESS);
url = `${gitAddress.protocol}//runner:${this.taskData.gitJWT}#${gitAddress.hostname}:${gitAddress.port}${this.taskData.repo}.git`;
}
// console.log(url);
// const url = `${gitAddress.protocol}//runner:${this.taskData.gitJWT}#${gitAddress.hostname}:${gitAddress.port}${this.taskData.repo}.git`;
this.logger.log('Cloning from', url);
return git.clone(url, 'repo', (cloneErr) => {
if (cloneErr) return reject(cloneErr);
// console.log(url);
// console.log(resolve);
return resolve(true);
});
});
});
}
}
I'm using Mocha and Chai to do this. I have two test for this function, to check the variable and the promise. The second test runs as expected, but the first always return fails with AssertionError: expected undefined not to be undefined. I think the issue is how I'm accessing the variable during testing. Currently I'm doing it like this: expect(result.url).to.not.be.undefined; Am I going about this correctly?
describe('Test MenloLab Runner - Task Class', () => {
describe('Pull Package', () => {
it('Check URL constant.', () => task.pullPackage().then((result) => {
expect(result.url).to.not.be.undefined; // adjust the access method
}));
it('It should pull package from GIT.', () => task.pullPackage().then((result) => {
expect(result).to.be.true;
}));
});
});
The workaround to check the URL could be done by spying on git.clone method. To do that, we need to use Sinon
I haven't tested with your code but I give you a clue on the solution below:
const sinon = require('sinon');
const git = require('simple-git');
describe('Test MenloLab Runner - Task Class', () => {
describe('Pull Package', () => {
it('Check URL constant.', () => {
return task.pullPackage().then((result) => {
sinon.spy(git, 'clone'); // spying on git.clone method
expect(result.url).to.not.be.undefined; // adjust the access method
const expectedUrl = 'my-expected-not-undefined-url';
sinon.assertCalledWith(git.clone, expectedUrl); // we check whether git.clone is called with not undefined URL
});
});
it('It should pull package from GIT.', () => task.pullPackage().then((result) => {
expect(result).to.be.true;
}));
});
});
Hope it helps

How do I unit test the result of a 'then' of a promise in JavaScript?

I'm using TypeScript to write a very simple service that utilizes the AWS SDK. My Jest unit tests are passing, but the coverage reports are saying that the line 'return result.Items' is not covered. Can anyone tell why this is? Is it a bug in jest?
// service file
/**
* Gets an array of documents.
*/
function list(tableName) {
const params = {
TableName: tableName,
};
return docClient
.scan(params)
.promise()
.then((result) => {
return result.Items;
});
}
// test file
const stubAwsRequestWithFakeArrayReturn = () => {
return {
promise: () => {
return { then: () => ({ Items: 'fake-value' }) };
},
};
};
it(`should call docClient.scan() at least once`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
it('should return result.Items out of result', async () => {
const mockAwsCall = jest
.fn()
.mockImplementation(stubAwsRequestWithFakeArrayReturn);
aws.docClient.get = mockAwsCall;
const returnValue = await db.get('fake-table', 'fake-id');
expect(returnValue).toEqual({ Items: 'fake-value' });
});
The line not covered is the success callback passed to then.
Your mock replaces then with a function that doesn't accept any parameters and just returns an object. The callback from your code is passed to the then mock during the test but it doesn't call the callback so Jest correctly reports that the callback is not covered by your tests.
Instead of trying to return a mock object that looks like a Promise, just return an actual resolved Promise from your mock:
const stubAwsRequestWithFakeArrayReturn = () => ({
promise: () => Promise.resolve({ Items: 'fake-value' })
});
...that way then will still be the actual Promise.prototype.then and your callback will be called as expected.
You should also await the returned Promise to ensure that the callback has been called before the test completes:
it(`should call docClient.scan() at least once`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
The Library chai-as-promised is worth looking at.
https://www.chaijs.com/plugins/chai-as-promised/
Instead of manually wiring up your expectations to a promise’s
fulfilled and rejected handlers.
doSomethingAsync().then(
function (result) {
result.should.equal("foo");
done();
},
function (err) {
done(err);
}
);
you can write code that expresses what you really mean:
return doSomethingAsync().should.eventually.equal("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');
} )
} );
} );

How to set a test for multiple fetches with Promise.all using jest

I'm using jest for my tests. I'm using react and redux and I have this action:
function getData(id, notify) {
return (dispatch, ...) => {
dispatch(anotherFunction());
Promise.all(['resource1', 'resource2', 'resource3'])
.then(([response1,response2,response3]) => {
// ... handle responses
})
.catch(error => { dispatch(handleError(error)); }
};
}
I've been looking for into the jest documentation how to set a test for this action, but I was unable to find a way. I tried myself something like this:
it('test description', (done) => {
const expectedActions = [{type: {...}, payload: {...}},{type: {...}, payload: {...}},...];
fetchMock.get('resource1', ...);
fetchMock.get('resource2', ...);
fetchMock.get('resource3', ...);
// ... then the rest of the test calls
});
Unsuccessfully. So how should I proceed?
To use Promise.all you could do the following
test('Testing Stuff', async (done) => {
const expectedActions = [{ foo: {...}, bar: {...} }, { foo: {...}, bar: {...} }];
// we pass the index to this function
const asyncCall = async (index) => {
// check some stuff
expect(somestuff).toBe(someOtherStuff);
// await the actual stuff
const response = await doStuff( expectedActions[index] );
// check the result of our stuff
expect(response).toBe(awesome);
return response;
};
// we put all the asyncCalls we want into Promise.all
const responses = await Promise.all([
asyncCall(0),
asyncCall(1),
...,
asyncCall(n),
]);
// this is redundant in this case, but wth
expect(responses).toEqual(awesome);
done();
});
You can tell Jest to wait for the promise to resolve by returning the promise in the callback. See this section here for more info.
it('should fetch some food', () => {
const fetchItem1 = () => fetchData1().then(data => {
expect(data).toBe('peanut butter');
})
const fetchItem2 = () => fetchData2().then(data => {
expect(data).toBe('boiled egg');
})
const fetchItem3 = () => fetchData3().then(data => {
expect(data).toBe('fried salad');
})
return Promise.all([fetchItem1(), fetchItem2(), fetchItem3()])
.then(() => runOtherTests());
});

Categories