Locked it method in chai - javascript

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

Related

Jest Unit Testing function that calls a second one that returns a promise

Edited Question with vazsonyidl suggestions applied
I have to write unit tests for a function similar to this one:
import {External} from 'ExternalModule';
async functionA(){
this.functionB().then((data) => {
External.functionC(options);
console.log("Reached1");
}).catch((data) => {
const { OnError = "" } = data || {}
if(OnError) {
External.functionC(anotherOptions);
console.log("Reached2");
}
})
}
functionB() {
return new Promise(() => {
});
}
As functionC belongs to another module, I placed a mock of it in the _mocks_folder:
//_mocks_/ExternalModule.ts
export var External: ExternalClass = {
functionC(){}
}
class ExternalClass{
constructor(){};
functionC(){};
}
I have mocked functionB in two diferent ways for testing the then and the catch :
it("should test then block", () => {
functionB = jest.fn(() => {return Promise.resolve()});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
it("should test catch block", () => {
const err = { OnError: "Error" };
functionB = jest.fn(() => {return Promise.reject(err)});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
What I am trying to do is expect that functionC was called and called with the correct params, but the test is always passing even if I test if functionC was not called.
What am I doing wrong?
Jest does not wait for the async code to complete before doing assertions.
You can use the following function:
const waitForPromises = () => new Promise(setImmediate);
to force Jest to wait for promises to complete before continuing like so:
it("does something", async () => {
promiseCall();
await waitForPromises();
expect(something).toBe(something)
});
I think when this function catch error, this error should have an 'OnError' property so the functionC can run.
const { OnError = "" } = data || {}
if(OnError) {
ExternalClass.functionC(anotherOptions);
}
change you response error data to return Promise.reject({OnError: '404'}) may solve this problem.
Because you are not providing it to your class.
The following code is working for me:
class A {
async functionA() {
this.functionB().then((data) => {
this.functionC(); // It woll log aaa here, you need this one.
}).catch((data) => {
const {OnError = ''} = data || {};
if (OnError) {
console.log('onerror');
}
});
}
functionB() {
return new Promise(() => {
});
}
functionC() {
return 2;
}
}
describe('a', () => {
it('test', () => {
const a = new A();
a.functionB = jest.fn(() => Promise.resolve());
const functionBSpy = jest.spyOn(a, 'functionC');
void a.functionA().then(() => {
expect(functionBSpy).toHaveBeenCalledTimes(1);
});
});
});
Hope this helps, any comment appreciated.
As you provided no information about your functionB I mocked something that may suitable for you.
Your original problem is that Jest does not wait for your callbacks to settle. It does the assertion although, even if your function calls happen later, Jest will not recognise them and says that no call ever occurred.
There are several docs available, for example Jest's one here

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");

how do i test my async jasmine/nodejs/promise code using Spies

I have a module (example has been simplified) called process-promise which has a single function that takes a Promise as input and processes it - it also calls other functions using modules outside it as follows:
//<process-promise.js>
let User = require('user-module');
let processPromise = (promiseObj) => {
let user = new User();
promiseObj.then((full_name) => {
const [ fname, sname ] = full_name.split(' ');
if (fname && sname) {
user.setDetails(fname, sname);
} else{
console.log('nothing happened');
}
}).catch((err) => {
console.log(err.message);
});
};
module.exports = {
processPromise
};
I am trying to unit test the above function using Jasmine, Rewire and Jasmine spies as per following code
let rewire = require('rewire');
let mod = rewire('process-promise');
describe('process-promise module', () => {
beforeEach(() => {
this.fakeUser = createSpyObj('fake-user', ['setDetails']);
this.fakeUserMod = jasmine.createSpy('fake-user-mod');
this.fakeUserMod.and.returnValue(this.fakeUser)
this.revert = mod.__set__({
User: this.fakeUserMod
});
});
afterEach(() => {
this.revert();
});
it('fakeUser.setDetails should be called', (done) => {
mod.processPromise(Promise.resolve('user name'));
done();
expect(this.fakeUser.setDetails).toHaveBeenCalledWith('user','name');
});
});
I expect that the Spy this.fakeUser.setDetails should get called but i get the message from Jasmine "Expected spy fake-user.setAll to have been called with [ 'user', 'name' ] but it was never called." - the problem seems to be the fact the promise is Async but i've included the done function as other SO questions have suggested but this doesn't seem to resolve the problem for me. What's the issue with my code? most other SO questions relate to angular so don't help with my problem.
You are on the right track, the promise is asynchronous and then done function in your test is called before the promise resolved to a value. The done function is used as a callback to tell the test engine, that all your asynchronous code has completed. It should be called after the promise resolved to a value (or failed for that matter).
In order to do that, you'd need to make the following adjustments to your code:
//<process-promise.js>
let User = require('user-module');
let processPromise = (promiseObj) => {
let user = new User();
// return a promise, to allow a client to chain a .then call
return promiseObj.then((full_name) => {
const [ fname, sname ] = full_name.split(' ');
if (fname && sname) {
user.setDetails(fname, sname);
} else{
console.log('nothing happened');
}
}).catch((err) => {
console.log(err.message);
});
};
module.exports = {
processPromise
};
The test would then look like this:
it('fakeUser.setAll should be called', (done) => {
mod.processPromise(Promise.resolve('user name')).then(() => {
expect(this.fakeUser.setAll).toHaveBeenCalledWith('user','name');
done();
}).catch(done);
});
Be sure to add .catch(done). This will make sure your test fails in case the promise resolves to an error.
Is probable that, by the time your test code execute, the promise has not propagated to the code under test. And simply calling done() doesn't the synchronization magic.
I'm not familiar with rewire so I will share an example using
proxyquire
const proxyquire = require('proxyquire');
describe('process-promise module', () => {
const fakeUser = { setDetails: jasmine.createSpy('setDetails') };
const fakeUserMod = jasmine.createSpy('fake-user-mod').and.returnValue(fakeUser);
const promiseObj = Promise.resolve('user name');
beforeEach((done) => {
const processPromiseMod = proxyquire('process-promise', {
'user-module': fakeUserMod,
});
processPromiseMod.processPromise(promiseObj);
promiseObj.then(() => done());
});
it('fakeUser.setDetails should be called', () => {
expect(fakeUser.setDetails).toHaveBeenCalledWith('user','name');
});
});
Also note that setAll doesn't exist in the fakeUser instance. I guess you mean setDetails instead of setAll.

Categories