How to test promises with Mocha - javascript

I'm using Mocha to test an asynchronous function that returns a promise.
What's the best way to test that the promise resolves to the correct value?

Mocha has built-in Promise support as of version 1.18.0 (March 2014). You can return a promise from a test case, and Mocha will wait for it:
it('does something asynchronous', function() { // note: no `done` argument
return getSomePromise().then(function(value) {
expect(value).to.equal('foo');
});
});
Don't forget the return keyword on the second line. If you accidentally omit it, Mocha will assume your test is synchronous, and it won't wait for the .then function, so your test will always pass even when the assertion fails.
If this gets too repetitive, you may want to use the chai-as-promised library, which gives you an eventually property to test promises more easily:
it('does something asynchronous', function() {
return expect(getSomePromise()).to.eventually.equal('foo');
});
it('fails asynchronously', function() {
return expect(getAnotherPromise()).to.be.rejectedWith(Error, /some message/);
});
Again, don't forget the return keyword!

Then 'returns' a promise which can be used to handle the error. Most libraries support a method called done which will make sure any un-handled errors are thrown.
it('does something asynchronous', function (done) {
getSomePromise()
.then(function (value) {
value.should.equal('foo')
})
.done(() => done(), done);
});
You can also use something like mocha-as-promised (there are similar libraries for other test frameworks). If you're running server side:
npm install mocha-as-promised
Then at the start of your script:
require("mocha-as-promised")();
If you're running client side:
<script src="mocha-as-promised.js"></script>
Then inside your tests you can just return the promise:
it('does something asynchronous', function () {
return getSomePromise()
.then(function (value) {
value.should.equal('foo')
});
});
Or in coffee-script (as per your original example)
it 'does something asynchronous', () ->
getSomePromise().then (value) =>
value.should.equal 'foo'

Related

Sinonjs test method that consumes a promise

I have a javascript method that looks something like this:
someMethod=function(){
somePromise.run().then(()=>{
//do success stuff
}).catch((err)=>{
//do fail stuff
registerError(err);
});
}
I want to make sure that the error is getting registered, so I've set up the following test.
it('should register error', ()=>{
somePromise = {
run: sinon.stub
};
registerError = sinon.stub;
somePromise.returns(Promise.reject({err: 'foo'}));
someMethod();
assert(registerError.calledWith({err: 'foo'}));
});
This test fails because the assert fires before the promises in someMethod finish. I could update someMethod to return a promise, but that smells fishy to me. Any input is welcome, thanks!
You have created an async method that does not return either a promise or contain a callback as an argument. This is not a good practice as you cannot know when your async function will finish. A simple fix is to return the promise from the function and perform your assert as a part of the promise chain.

How to chain promises through a callback in JavaScript?

I am currently writing an integration test with Mocha, Chai, and WebdriverIO. The WebdriverIO syntax requires a chain of promises to navigate through the browser, an example of which is below:
it('sign in and out test', function() {
return client
.url(Page.url)
.pause(20000)
.waitForVisible(HomePage.signInDropdown, 10000)
.click(HomePage.signInDropdown)
}
This leads to long blocks of code with every step laid out explicitly. Since some of the steps (such as logging in and logging out) are used frequently throughout different tests, I want to modularize those snippets of code through callbacks. However, the following syntax, which works without a nested callback, fails:
function myCallback(){
console.log('This is where I''d add promises');
}
it('sign in and out test',function() {
return client
.url(Page.url)
.pause(20000)
.then(function() {
myCallback();
}
.waitForVisible(HomePage.signInDropdown, 10000)
.click(HomePage.signInDropdown)
}
The following different syntax in the callback also fails:
function myCallback(){
return client
.url(Page.url)
console.log('This is a callback using return');
}
Given that promises are mostly intended to replace callbacks, is it possible in this special case to continue the promise chain through a callback?
myCallback() is not returned from .then().
.then(function() {
// `return` value from `myallback()`
return myCallback();
})
after logout promise resolve we need to do other works in chain.
Please check this,
function myCallback(){
return client
.url(Page.url)
}
it('sign in and out test',function() {
return client
.url(Page.url)
.pause(20000)
.then(function() {
myCallback()
.waitForVisible(HomePage.signInDropdown, 10000)
.click(HomePage.signInDropdown);
}
}

Mocha throws "Resolution method is overspecified"

I'm trying to write a simple test -
describe("My test", function(){
it("Succeed", function*(done){
yield testFunc();
done()
})
})
function *testFunc(){
console.log("Hey!")
}
Note that I'm using co-mocha so that my generator will be executed.
The error that I get is -
Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
Now, the docs clearly state -
when a test injects a callback function (suggesting asynchronous execution), calls it, and returns a Promise, Mocha will now throw an exception
https://github.com/mochajs/mocha/blob/master/CHANGELOG.md#boom-breaking-changes
However, I'm not returning a Promise! I'm injecting the done function which is a callback, but I'm not returning a Promise... yielding testFunc doesn't return a Promise.
So, why is this test throwing an error?
Thanks
The generator (function*) or rather it's coroutine-creating wrapper that's implicit here is most likely returning a promise. I'm not exactly sure how the co-mocha works here but maybe this would work:
describe("My test", function () {
it("Succeed", function* () {
yield testFunc();
return;
});
});
The return is not needed here, just added for clarity.
But you may need to change:
function *testFunc(){
console.log("Hey!")
}
to something like:
let testFunc = co.wrap(function* () {
console.log("Hey!");
});
for your yield in the code above to work.
If you're using co coroutines then your generators should yield promises. Here, you're generator yields the result of running a generator function that itself returns a generator, not a promise.
Your assumption that no promise is used is incorrect. co-mocha uses co.wrap internally to wrap the functions you pass to it. The documentation for co.wrap says:
Convert a generator into a regular function that returns a Promise.
Emphasis added.
Removing done as a param worked for me! Example is as follows:
BEFORE:
it('receives successful response', async (done) => {
const getSomeData = await getResponse(unitData, function callBack(unit, error, data){
expect(data.statusCode).to.be.equal(200);
done();
}) })
AFTER (works):
it('receives successful response', async () => {
const getSomeData = await getResponse(unitData, function callBack(unit, error, data){
expect(data.statusCode).to.be.equal(200);
}) })

Why is my async Jest test not failing when it should?

I have some asynchronous actions that I need to test with Jest. My test is currently passing when it should fail.
describe('Asynchronous Code', () => {
it('should execute promise', () => {
console.log(1);
someFunctionThatReturnsAPromise()
.then(() => {
console.log(2);
expect(true).toBeFalsy();
console.log(3);
});
console.log(4);
});
});
When I run npm test, I get the following output:
PASS __tests__/Async.test.js
● Console
console.log __tests__/Async.test.js:3
1
console.log static-content-test/react/actions/DashboardActions.test.js:6
2
console.log static-content-test/react/actions/DashboardActions.test.js:10
4
As you can see, the test is passing, but console.log(3) is never executed because true is not falsy, and the expectation fails.
How can I get Jest to recognize my expectations inside async callbacks?
When testing asynchronous code, you need to return the promise from the test. Change the test body to:
return someFunctionThatReturnsAPromise()
.then(() => {
expect(true).toBeFalsy();
});
With that, the test fails as expected:
FAIL __tests__/Async.test.js
● Asynchronous Code › should execute promise
expect(received).toBeFalsy()
Expected value to be falsy, instead received
true
This is the pattern facebook uses for testing async code with jest.
Alternatively, you can follow the done pattern as described here:
it('should execute promise', (done) => {
someFunctionThatReturnsAPromise()
.then(() => {
expect(true).toBeFalsy();
done();
});
});
This will work with Jest, but is more commonly used with Jasmine and Mocha.
Here is the alternate solution.
Jest will be terminated once it reaches the end of context. So you need to return the promise from the callback to tell it to wait for the promise to get resolved and tested.
Lets say there is a promise
const promise=fetch("blah.com/api")
test("should return valid data",()=>{
return expect(promise).resolves.toBeTruthy()
})
.resolves waits for the promise to resolve, and you apply appropriate
matchers as your wish.
And also you can use .rejects when you are checking for the error cases.

Do I really need to return a promise in test when using Chai as Promised?

Chai as Promised documentation states as follows:
Notice: either return or notify(done) must be used with promise assertions.
And the examples on the site are as follows:
return doSomethingAsync().should.eventually.equal("foo");
doSomethingAsync().should.eventually.equal("foo").notify(done);
The thing is; I actually wrote a test using chai as promised without returning the promise. Like so:
it('should resolve user', function () {
$state.get(state).resolve.user(dataservice, {
userId: testUser.id
}).should.eventually.eq(testUser);
$rootScope.$apply();
});
And it works perfectly fine. I am sure it does as I change testUser to something else the test fails. Just like I expected. So I am not sure if I am doing something wrong here.
In fact, when I modified the code to return a promise, it failed with error "Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test." The modified code is below:
it('should resolve user', function () {
var promise = $state.get(state).resolve.user(dataservice, {
userId: testUser.id
}).should.eventually.eq(testUser);
$rootScope.$apply();
return promise;
});
A little confused here. It might have something to do with Angular $q. To make it clear, the function resolve.user returns a $q promise.
In the case above Mocha chains returned promise after $rootScope.$apply() was called, so chained then needs another $rootScope.$apply() to be executed. Without this the rest of promise chain is not executed and results in timeout.
Returning promises in Mocha specs is intended for asynchronous specs, this is necessary for testing non-Angular promises. $q promises are synchronous and tied to Angular digests.
As shown here, chai-as-promised can be modified to support $q promises and apply $rootScope.$apply() automatically to asserted promises:
chaiAsPromised.transferPromiseness = function (assertion, promise) {
assertion.then = promise.then.bind(promise);
if (!('$$state' in promise))
return;
inject(function ($rootScope) {
if (!$rootScope.$$phase)
$rootScope.$digest();
});
};

Categories