How to checking function called in Promise by sinon - javascript

I was using mocha + chai + sinon to test function, but in promise use called property, even if write a undefined function, called always true, What is it caused by ?and how to resolve? thx
describe("when click get verifycode", function () {
it('if get server response success, should start countdown', function (done) {
// given
sandbox.stub(model, 'getVerifyCode').resolves({});
sandbox.spy(view, 'startCountDown');
// when
var result = instance.onClickVerify('123456');
// then
result.then(res => {
// no matter what function, called always true
// e.g. expect(AnUndefinedFunction.called).to.be.ok;
expect(instance.view.startCountDown.called).to.be.ok;
done();
}).catch(err => {
done();
});

The problem here is that the .catch block is catching any error that occurs in the .then block.
This is an issue because you are calling done without passing the err object. Mocha interprets this as the test passing correctly, rather than failing.
The fix:
describe("when click get verifycode", function () {
it('if get server response success, should start countdown', function (done) {
// given
sandbox.stub(model, 'getVerifyCode').resolves({});
sandbox.spy(view, 'startCountDown');
// when
var result = instance.onClickVerify('123456');
// then
result.then(res => {
// no matter what function, called always true
// e.g. expect(AnUndefinedFunction.called).to.be.ok;
expect(instance.view.startCountDown.called).to.be.ok;
done();
}).catch(err => {
done(err);
});
})
});

Related

How to write a test using Mocha+Chai to expect an exception from setTimeout?

I have following:
it('invalid use', () => {
Matcher(1).case(1, () => {});
});
The case method is supposed to throw after some delay, how can I describe it for Mocha/Chai that's what I want - the test should pass (and must not pass when exception is not thrown)?
Consider case method off limits, it cannot be changed.
For testing purposes it should be equivalent to:
it('setTimeout throw', _ => {
setTimeout(() => { throw new Error(); }, 1); // this is given, cannot be modified
});
I tried:
it('invalid use', done => {
Matcher(1).case(1, () => {});
// calls done callback after 'case' may throw
setTimeout(() => done(), MatcherConfig.execCheckTimeout + 10);
});
But that's not really helping me, because the test behavior is exactly reverted - when an exception from case (setTimeout) is not thrown, it passes (should fail) and when an exception is thrown the test fails (should succeed).
I read somewhere someone mentioning global error handler, but I would like to solve this cleanly using Mocha and/or Chai, if it is possible (I guess Mocha is already using it in some way).
You cannot handle exceptions from within a asynchronous callback, e.g. see Handle error from setTimeout. This has to do with the execution model ECMAScript uses. I suppose the only way to catch it is in fact to employ some environment-specific global error handling, e.g. process.on('uncaughtException', ...) in Node.js.
If you convert your function to Promises, however, you can easily test it using the Chai plugin chai-as-promsied:
import * as chai from 'chai';
import chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
it('invalid use', async () => {
await expect(Matcher(1).case(1, () => {})).to.eventually.be.rejected;
});
Any Mocha statements like before, after or it will work asynchronously if you return a promise. I generally use something like the below for async tests.
Also don't forget to set timeout this.timeout(...) if you expect the async function to take more than 2 seconds.
it('some test', () => {
return new Promise(function(resolve,reject){
SomeAsyncFunction(function(error,vals) {
if(error) {
return reject(error);
} else {
try {
//do some chai tests here
} catch(e) {
return reject(e);
}
return resolve();
}
});
});
});
Specifically for your case, since we expect some error to be thrown after a period of time (assuming the empty callback you have provided to .case should not run due to the exception being thrown) then you can write it something like:
it('invalid use', () => {
//define the promise to run the async function
let prom = new Promise(function(resolve,reject){
//reject the promise if the function does not throw an error
//note I am assuming that the callback won't run if the error is thrown
//also note this error will be passed to prom.catch so need to do some test to make sure it's not the error you are looking for.
Matcher(1).case(1, () => {return reject(new Error('did not throw'))});
});
prom.catch(function(err){
try {
expect(err).to.be.an('error');
expect(err.message).to.not.equal('did not throw');
//more checks to see if err is the error you are looking for
} catch(e) {
//err was not the error you were looking for
return Promise.reject(e);
}
//tests passed
return Promise.resolve();
});
//since it() receives a promise as a return value it will pass or fail the test based on the promise.
return prom;
});
From Chai documentation :
When no arguments are provided, .throw invokes the target function and asserts that an error is thrown.
So you could something like
expect(Matcher(1).case(1, () => {})).to.throw
If your tested code calls setTimeout with a callback that throws and no-one is catching this is exception then:
1) this code is broken
2) the only way to see that problem is platform global exception handler like process.on('uncaughtException' mentioned by user ComFreek
The last resort chance is to stub setTimeout for duration of test (for example using sinon.stub) or just manually.
In such stubbed setTimeout you can decorate timeout handler, detect exception and call appropriate asserts.
NOTE, this is last resort solution - your app code is broken and should be fixed to properly propagate errors, not only for testing but ... well, to be good code.
Pseudocode example:
it('test', (done) => {
const originalSetTimeout = setTimeout;
setTimeout = (callback, timeout) => {
originalSetTimeout(() => {
try {
callback();
} catch(error) {
// CONGRATS, you've intercepted exception
// in _SOME_ setTimeout handler
}
}, timeout)
}
yourTestCodeThatTriggersErrorInSomeSetTimeoutCallback(done);
})
NOTE2: I intentionally didn't wrote proper async cleanup code, it's a homework. Again, see sinon.js and its sandbox
NOTE3: It will catch all setTimeout calls during test duration. Beware, there are dragons.

Uncaught (in promise) for Mock-promises

I went through other similar posts and they all mention that I need to specify catch block with my promise instead of second param in then(onSuccess, onRejected).
However I realize if I change myPromise.then(onSuccess, onRejected) to myPromise.then(onSuccess).catch(onRejected) then catch block is never executed and error still shows up in chrome dev console.
// Verifiers
const verifyOnFulfilled = (result) => {
expect(result).toEqual(expectedOnFulfilledResponse);
};
const verifyOnRejected = (result) => {
expect(result).toEqual(expectedOnRejectedResponse);
};
// Mocking a server call and this catch statement does nothing.
// Added just to test if this fixes the prob
mockPromise.catch((e) => {
return e;
});
// Mock request bind. Ignore this
requestSpy.and.returnValue(mockPromise);
// Call the api
const promiseA = myService.deleteItem(id); // It will create a new mock promise
promiseA.then(verifyOnFulfilled, verifyOnRejected);
// promiseA.then(verifyOnFulfilled).catch(verifyOnRejected); // If I use this then catch function is never executed.
// Run mock promises
MockPromises.executeForPromise(promiseA);
API call block:
let deleteRequestPromise = ... // some internal generic way to create promise
return deleteRequestPromise.then(
() => {
return commentId;
},
(error) => {
return Promise.reject(error);
}
);

Properly call mocha done callback and promises

I have several integration tests in my app:
it('creating vehicle', function (done) {
createVehicle()
.then(() => {
done();
})
.catch((err) => {
done(err);
});
});
createVehicle makes post request and returns promise:
return request.json('post', '/api/vehicle/')
.send(obj)
.expect(200)
.then((res) => {
expect(res.body).toExist("Body is empty");
expect(res.body.id).toExist("Id is empty");
return res.body;
});
Now all works fine, but if I rewrite first snippet in the following way:
it('creating vehicle', function (done) {
createVehicle()
.then(done) //*
.catch(done); //*
});
I get error from Mocha
done() invoked with non-Error
I understand why. The createVehicle return res.body and it's passed to then callback, in result done run as done(arg) and I get the error, because mocha done callback has to be called without arg when there is no error and with argument when there is error.
Is it possible to use this variant:
.then(done)
.catch(done);
How to achieve this?
Of course, I can delete return statement, but createVehicle is used in several places and I need returned value:
it('creating vehicle with media', function (done) {
createVehicle()
.then(createMedia) //createMedia will get returned value
//....
});
The easiest solution would be to just return the promise instead of having to deal with callbacks, because Mocha supports promises out-of-the-box:
it('creating vehicle', function() {
return createVehicle();
});

When testing async functions with Mocha/Chai, a failure to match an expectation always results in a timeout

For example, I have something basic like this:
it.only('tests something', (done) => {
const result = store.dispatch(fetchSomething());
result.then((data) => {
const shouldBe = 'hello';
const current = store.something;
expect(current).to.equal(shouldBe);
done();
}
});
When current does not match shouldBe, instead of a message saying that they don't match, I get the generic timeout message:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being
called in this test.
It's as if the expectation is pausing the script or something. How do I fix this? This is making debugging nearly impossible.
The expectation is not pausing the script, it is throwing an exception before you hit the done callback, but since it is no longer inside of the test method's context it won't get picked up by the test suite either, so you are never completing the test. Then your test just spins until the timeout is reached.
You need to capture the exception at some point, either in the callback or in the Promise's error handler.
it.only('tests something', (done) => {
const result = store.dispatch(fetchSomething());
result.then((data) => {
const shouldBe = 'hello';
const current = store.getState().get('something');
try {
expect(current).to.equal(shouldBe);
done();
} catch (e) {
done(e);
}
});
});
OR
it.only('tests something', (done) => {
const result = store.dispatch(fetchSomething());
result.then((data) => {
const shouldBe = 'hello';
const current = store.getState().get('something');
expect(current).to.equal(shouldBe);
})
.catch(done);
});
Edit
If you are not opposed to bringing in another library, there is a fairly nice library call chai-as-promised. That gives you some nice utilities for this kind of testing.

Mocha testing a bluebird powered node style callback

I am facing trouble to get pass a test by running mocha which seems to be passing.
The test:
describe('.get()',function() {
it('should be called once',function() {
// => Need to spy on this
var callback = function(err,data) {
console.log('I am called');
if (err) {
console.log('I am logging the error '+err);
} else {
console.log('I am logging the data '+data);
}
}
agentMock._response = {body:'something',headers:'headers'};
// => Start Spying
var spy = sinon.spy(callback);
sinon.spy(agentMock,'get');
baseRequest.get(spy); // refer (a) below
expect(agentMock.get).to.have.been.calledOnce;
expect(spy).to.have.been.calledOnce;
expect(spy).to.have.been.calledWith(null,'data');
});
});
I want to test whether the callback is called or not. Therefore, I logged in the body of the callback, and the stdout also suggests it's being called.
The stdout :
.get()
1) should be called once
I am called
I am logging the data something
0 passing (15ms)
1 failing
1) .get() should be called once:
AssertionError: expected spy to have been called exactly once, but it was called 0 times
Details:
(a) baseRequest.get return the data as a bluebird promise. This can be used by passing in a nodeback to .get itself or by chaining .then after .get call.
BaseRequest.prototype.get = function(callback) {
// inner details
return invokeandPromisify(request,callback);
}
function invokeandPromisify(request, callback) {
return new Promise(function(resolve,reject) {
// Invoke the request
request.end(function(err,result) {
// Return the results as a promise
if (err || result.error) {
reject(err || result.error);
} else {
resolve(result);
}
});
}).nodeify(callback); // to have a node style callback
}
Does it happen because the callback on which I want to spy is passed to a different function( invokeandPromisify here ) and the spying is lost ? I am just interpreting this.
Regards.
Since baseRequest#get returns a promise, I would make the assertions after the promise is resolved.
See example below:
it('should be called once',function(done) {
// => Need to spy on this
var callback = function(err,data) {
console.log('I am called');
if (err) {
console.log('I am logging the error '+err);
} else {
console.log('I am logging the data '+data);
}
}
agentMock._response = {body:'something',headers:'headers'};
// => Start Spying
var spy = sinon.spy(callback);
sinon.spy(agentMock,'get');
baseRequest.get(spy).finally(function() {
expect(agentMock.get).to.have.been.calledOnce;
expect(spy).to.have.been.calledOnce;
expect(spy).to.have.been.calledWith(null,'data');
done();
});
});
Your test should be set as async by adding done. Then in your callback funtion call done()
Please check http://mochajs.org/#asynchronous-code

Categories

Resources