How do I resolve Promise in mocked function? - javascript

I'm trying to test method which returns Promise and resolves in another method which I want to mock.
I can test it without mocking but I don't want to because of credentials which wouldn't work on another machine.
Here's my Service code (PollyService.ts):
#Service()
export class PollyService {
#Inject()
private polly: Polly; // aws-sdk Polly
url: string;
getSpeech(body: any) {
return new Promise((resolve) => {
let voice: string = (voices as any)[body.code];
let params = {
exampleParam: example
};
this.polly.synthesizeSpeech(params, (error: any, data: any) => {
if (error) throw error;
else {
resolve(data);
}
});
});
}
}
Here's test (app.spec.ts)
describe('PollyService', () => {
afterEach(() => {
Container.reset();
});
it ('Should return data', async () => {
const event: any = {
body: {
sentence: "potato",
code: "en"
}
};
let polly = Container.get(Polly); // aws-sdk polly
spyOn(polly, 'synthesizeSpeech'); // and here I want to resolve Promise
await Container.get(PollyService).getSpeech(event.body);
});
});
When I am mocking that without resolving promise there is error:
Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
How can i simulate resolving Promise in tests?

If you want to create a promise which is already resolved, you can simply use Promise.resolve(someData)
In your case it should be something like this:
spyOn(polly, 'synthesizeSpeech').and.returnValue(Promise.resolve('mockedData'));
Note: You get the timeout error message because you are neither returning the promise in the end of your function nor make use of the done callback.
either do:
...
return await Container.get(PollyService).getSpeech(event.body);
or
describe('PollyService', () => {
afterEach(() => {
Container.reset();
});
it ('Should return data', async (done) => { // <- done is the callback
const event: any = {
body: {
sentence: "potato",
code: "en"
}
};
let polly = Container.get(Polly);
spyOn(polly, 'synthesizeSpeech');
await Container.get(PollyService).getSpeech(event.body);
done(); // <- calling it will tell jasmine your test is over
});
});
Usually either you choose to return the promise or use the callback. I am not sure on how jasmine mix async methods and the callback in the same method but it should work. More information here

You should extend the spyOn with and.returnValue as follows:
const data = ... // define the data to be returned by synthesizeSpeech
spyOn(polly, 'synthesizeSpeech').and.returnValue(Promise.resolve(data));

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

Jest test the resolve reject callback

I have this function which calls an util function for api calls. The util function resolves or rejects based on the api result.
Now, I need to unit test the callback functions which has the following structure.
`theClassMethod : () => {
return utilMethod().then(
result => { this.functionOne() //Test this function is called },
error => { this.functionTwo() //Test this function is called }
)
}`
The util method returns a promise like below:
utilFunc = (data :string) :Promise<ResultData[]> => {
return new Promise(async (resolve, reject) => {
try{
resolve(data)
}catch{
reject(error)
}
})
}
https://codesandbox.io/s/vjnwy1zw75?fontsize=14
What I tried:
Mocked the util method to resolve/reject. Call the class method and do assertions. It doesn't work and the test always passes as a false positive.
I have spend much time looking for a similar problem. Most questions here are to test the code like:
theClassMethod : () => { utilMethod.then().catch()}
The problem I am trying to solve is to test the resolve, reject callbacks in the then block then(function1, function2). That the code block inside function1 has to be tested that it calls some intended functions.
The approach you are describing (mocking utilMethod to resolve/reject) is a good approach.
Here is a simple working example to get you started:
Note: I implemented functionOne as a class method and functionTwo as an instance property to show how to spy on both types of functions:
util.js
export const utilMethod = async () => 'original';
code.js
import { utilMethod } from './util';
export class MyClass {
functionOne() { } // <= class method
functionTwo = () => { } // <= instance property
theClassMethod() {
return utilMethod().then(
result => { this.functionOne() },
error => { this.functionTwo() }
);
}
}
code.test.js
import { MyClass } from './code';
import * as util from './util';
test('theClassMethod', async () => {
const mock = jest.spyOn(util, 'utilMethod');
const instance = new MyClass();
const functionOneSpy = jest.spyOn(MyClass.prototype, 'functionOne'); // <= class method
const functionTwoSpy = jest.spyOn(instance, 'functionTwo'); // <= instance property
mock.mockResolvedValue('mocked value'); // <= mock it to resolve
await instance.theClassMethod();
expect(functionOneSpy).toHaveBeenCalled(); // Success!
mock.mockRejectedValue(new Error('something bad happened')); // <= mock it to reject
await instance.theClassMethod();
expect(functionTwoSpy).toHaveBeenCalled(); // Success!
});

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

Jest: Can only re-mock implementation when running a single test

I have the following simplified example where I'm manually mocking a dependency and getting it to return a resolved Promise. In one of my tests I then attempt to change its return type to a Promise rejection.
If I run all 3 tests together they fail, because the call in the test for rejection actually resolves. However, if I modify line 17 in the spec to it.only('should reject if the client fails to load' then the single test passes.
What am I doing wrong?
service.js:
import client from './client';
const service = (() => {
let instance;
const makeCall = () => {
return true;
};
const init = async () => {
const _client = await client();
return Promise.resolve({
makeCall,
});
};
const getInstance = async () => {
if (!instance) {
instance = await init();
}
return Promise.resolve(instance);
};
return {
getInstance,
};
})();
export default service;
client.js:
const client = () => Promise.resolve('Real');
export default client;
__test__/service.spec.js:
import client from '../client';
import service from '../service';
jest.mock('../client');
describe('Service', () => {
it('should return a singleton', () => {
expect(service.getInstance()).toEqual(service.getInstance());
});
it('should resolve if the client successfully loads', async () => {
expect.assertions(1);
await expect(service.getInstance()).resolves.toHaveProperty('makeCall');
});
it('should reject if the client fails to load', async () => {
const errorMessage = 'Client could not be initialised.';
expect.assertions(1);
client.mockReturnValueOnce(Promise.reject(new Error(errorMessage)));
await expect(service.getInstance()).rejects.toHaveProperty(
'message',
errorMessage
);
});
});
__mocks__/client.js:
const client = jest.fn();
client.mockImplementation(() => {
return Promise.resolve('Mock file');
});
export default client;
This is caused by trying to mock the dependencies of a Singleton.
When running all three tests, the original mock implementation (that resolves) is used and is then not replaced by the second mock implementation that rejects.
It's also worth noting that because the rejected Promise is created but therefore not handled, this results in an unhandled Promise rejection and the test suite crashes.

Is there a way to get Chai working with asynchronous Mocha tests?

I'm running some asynchronous tests in Mocha using the Browser Runner and I'm trying to use Chai's expect style assertions:
window.expect = chai.expect;
describe('my test', function() {
it('should do something', function (done) {
setTimeout(function () {
expect(true).to.equal(false);
}, 100);
}
}
This doesn't give me the normal failed assertion message, instead I get:
Error: the string "Uncaught AssertionError: expected true to equal false" was thrown, throw an Error :)
at Runner.fail (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3475:11)
at Runner.uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3748:8)
at uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3778:10)
So it's obviously catching the error, it's just not displaying it correctly. Any ideas how to do this? I guess I could just call "done" with an error object but then I lose all the elegance of something like Chai and it becomes very clunky...
Your asynchronous test generates an exception, on failed expect()ations, that cannot be captured by it() because the exception is thrown outside of it()'s scope.
The captured exception that you see displayed is captured using process.on('uncaughtException') under node or using window.onerror() in the browser.
To fix this issue, you need to capture the exception within the asynchronous function called by setTimeout() in order to call done() with the exception as the first parameter. You also need to call done() with no parameter to indicate success, otherwise mocha would report a timeout error because your test function would never have signaled that it was done:
window.expect = chai.expect;
describe( 'my test', function() {
it( 'should do something', function ( done ) {
// done() is provided by it() to indicate asynchronous completion
// call done() with no parameter to indicate that it() is done() and successful
// or with an error to indicate that it() failed
setTimeout( function () {
// Called from the event loop, not it()
// So only the event loop could capture uncaught exceptions from here
try {
expect( true ).to.equal( false );
done(); // success: call done with no parameter to indicate that it() is done()
} catch( e ) {
done( e ); // failure: call done with an error Object to indicate that it() failed
}
}, 100 );
// returns immediately after setting timeout
// so it() can no longer catch exception happening asynchronously
}
}
Doing so on all your test cases is annoying and not DRY so you might want to provide a function to do this for you. Let's call this function check():
function check( done, f ) {
try {
f();
done();
} catch( e ) {
done( e );
}
}
With check() you can now rewrite your asynchronous tests as follows:
window.expect = chai.expect;
describe( 'my test', function() {
it( 'should do something', function( done ) {
setTimeout( function () {
check( done, function() {
expect( true ).to.equal( false );
} );
}, 100 );
}
}
Here are my passing tests for ES6/ES2015 promises and ES7/ES2016 async/await. Hope this provides a nice updated answer for anyone researching this topic:
import { expect } from 'chai'
describe('Mocha', () => {
it('works synchronously', () => {
expect(true).to.equal(true)
})
it('works ansyncronously', done => {
setTimeout(() => {
expect(true).to.equal(true)
done()
}, 4)
})
it('throws errors synchronously', () => {
return true
throw new Error('it works')
})
it('throws errors ansyncronously', done => {
setTimeout(() => {
return done()
done(new Error('it works'))
}, 4)
})
it('uses promises', () => {
var testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello')
}, 4)
})
testPromise.then(result => {
expect(result).to.equal('Hello')
}, reason => {
throw new Error(reason)
})
})
it('uses es7 async/await', async (done) => {
const testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello')
}, 4)
})
try {
const result = await testPromise
expect(result).to.equal('Hello')
done()
} catch(err) {
done(err)
}
})
/*
* Higher-order function for use with async/await (last test)
*/
const mochaAsync = fn => {
return async (done) => {
try {
await fn()
done()
} catch (err) {
done(err)
}
}
}
it('uses a higher order function wrap around async', mochaAsync(async () => {
const testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello')
}, 4)
})
expect(await testPromise).to.equal('Hello')
}))
})
If you like promised, try Chai as Promised + Q, which allow something like this:
doSomethingAsync().should.eventually.equal("foo").notify(done);
I asked the same thing in the Mocha mailing list. They basically told me this : to write asynchronous test with Mocha and Chai :
always start the test with if (err) done(err);
always end the test with done().
It solved my problem, and didn't change a single line of my code in-between (Chai expectations amongst other). The setTimout is not the way to do async tests.
Here's the link to the discussion in the mailing list.
I've published a package that resolves this issue.
First install the check-chai package:
npm install --save check-chai
Then in your tests, use chai.use(checkChai); and then use the chai.check helper function as shown below:
var chai = require('chai');
var dirtyChai = require('dirty-chai');
var checkChai = require('check-chai');
var expect = chai.expect;
chai.use(dirtyChai);
chai.use(checkChai);
describe('test', function() {
it('should do something', function(done) {
// imagine you have some API call here
// and it returns (err, res, body)
var err = null;
var res = {};
var body = {};
chai.check(done, function() {
expect(err).to.be.a('null');
expect(res).to.be.an('object');
expect(body).to.be.an('object');
});
});
});
Per Is there a way to get Chai working with asynchronous Mocha tests? I published this as an NPM package.
Please see https://github.com/niftylettuce/check-chai for more information.
Try chaiAsPromised! Aside from being excellently named, you can use statements like:
expect(asyncToResultingValue()).to.eventually.equal(true)
Can confirm, works very well for Mocha + Chai.
https://github.com/domenic/chai-as-promised
Very much related to and inspired by Jean Vincent's answer, we employ a helper function similar to his check function, but we call it eventually instead (this helps it match up with the naming conventions of chai-as-promised). It returns a function that takes any number of arguments and passes them to the original callback. This helps eliminate an extra nested function block in your tests and allows you to handle any type of async callback. Here it is written in ES2015:
function eventually(done, fn) {
return (...args) => {
try {
fn(...args);
done();
} catch (err) {
done(err);
}
};
};
Example Usage:
describe("my async test", function() {
it("should fail", function(done) {
setTimeout(eventually(done, (param1, param2) => {
assert.equal(param1, "foo"); // this should pass
assert.equal(param2, "bogus"); // this should fail
}), 100, "foo", "bar");
});
});
I know there are many repeat answers and suggested packages to solve this however I haven't seen the simple solutions above offer a concise pattern for the two use cases. I am posting this as a consolidated answer for other who wish to copy-pasta:
event callbacks
function expectEventCallback(done, fn) {
return function() {
try { fn(...arguments); }
catch(error) { return done(error); }
done();
};
}
node style callbacks
function expectNodeCallback(done, fn) {
return function(err, ...args) {
if (err) { return done(err); }
try { fn(...args); }
catch(error) { return done(error); }
done();
};
}
example usage
it('handles event callbacks', function(done) {
something.on('event', expectEventCallback(done, (payload) => {
expect(payload).to.have.propertry('foo');
}));
});
it('handles node callbacks', function(done) {
doSomething(expectNodeCallback(done, (payload) => {
expect(payload).to.have.propertry('foo');
}));
});
Based on this link provided by #richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/, describe can use a returned Promise if you omit the done parameter.
Only downside there has to be a Promise there, not any async function (you can wrap it with a Promise, thou). But in this case, code can be extremely reduced.
It takes into account failings from either in the initial funcThatReturnsAPromise function or the expectations:
it('should test Promises', function () { // <= done removed
return testee.funcThatReturnsAPromise({'name': 'value'}) // <= return added
.then(response => expect(response).to.have.property('ok', 1));
});
I solved it extracting try/catch to a function.
function asyncExpect(test, done){
try{
test();
done();
} catch(error){
done(error);
}
}
Then in it() I call:
it('shall update a host', function (done) {
testee.insertHost({_id: 'host_id'})
.then(response => {
asyncExpect(() => {
expect(response).to.have.property('ok', 1);
expect(response).to.have.property('nModified', 1);
}, done);
});
});
It's also debugable.
Timers during tests and async sounds pretty rough. There is a way to do this with a promise based approach.
const sendFormResp = async (obj) => {
const result = await web.chat.postMessage({
text: 'Hello world!',
});
return result
}
This async function uses a Web client (in this case it is Slacks SDK). The SDK takes care of the asynchronous nature of the API call and returns a payload. We can then test the payload within chai by running expect against the object returned in the async promise.
describe("Slack Logic For Working Demo Environment", function (done) {
it("Should return an object", () => {
return sdkLogic.sendFormResp(testModels.workingModel).then(res => {
expect(res).to.be.a("Object");
})
})
});
A simpler approach would be using wait-for-expect library.
const waitForExpect = require("wait-for-expect")
test("it waits for the number to change", async () => {
let numberToChange = 10;
setTimeout(() => {
numberToChange = 100;
}, randomTimeout);
await waitForExpect(() => {
expect(numberToChange).toEqual(100);
});
});
What worked very well for me icm Mocha / Chai was the fakeTimer from Sinon's Library.
Just advance the timer in the test where necessary.
var sinon = require('sinon');
clock = sinon.useFakeTimers();
// Do whatever.
clock.tick( 30000 ); // Advances the JS clock 30 seconds.
Has the added bonus of having the test complete quicker.
You can also use domain module. For example:
var domain = require('domain').create();
domain.run(function()
{
// place you code here
});
domain.on('error',function(error){
// do something with error or simply print it
});

Categories