I am new to asynchronous programming. I am writing a test case for the following example code.
someAsync(text) {
try {
//do something and resolve result
return Promise.resolve(result);
} catch (err) {
return Promise.reject(new Error(`Failure ${err}`));
}
}
I am testing it with the following code:
it('should throw error when called', (done) => {
const mymodule = new MyModule(args);
mymodule.someAsync('something that causes failure').catch((err) => {
expect(err).to.exist;
expect(err.message).to.contains('This should pass');
done(err);
});
});
This test case fails, assertions pass and then after done again it throws error.
Please tell me where am I going wrong?
done(err) makes a spec to fail. Since it's expected error, it shouldn't fail a spec, it should be done() instead.
Mocha doesn't need done to handle promises, a promise can be returned from a spec.
It likely should be:
it('should throw error when called', () => {
const mymodule = new MyModule(args);
return mymodule.someAsync('something that causes failure').catch((err) => {
expect(err).to.exist;
expect(err.message).to.contain('This should pass');
});
});
Also, it's not evident from the listed code that expect(err.message).to.contain('This should pass') assertion is true.
This might or might not have anything to do with your error, but that does not really look like asynchronous code. Something like this might make more sense:
const someAsync = (text) => new Promise((resolve, reject)) => {
try {
// do something and capture result
resolve(result);
} catch (err) {
reject(new Error(`Failure ${err}`));
}
})
As estus' comment said, we probably need the actual error and the test framework to diagnose further.
Related
I have a function that returns a promise, but I want to test that it has a catch defined, and then additionally test that it re-throws the error.
This is a very contrived example but it was the clearest way to show the issue. In my actual code, I am calling a function that is mocked to fail (vs the manually rejecting in this example), and I have additional logging in the catch statement, which explains the re-throwing of the error.
const foo = () => {
return new Promise((resolve, reject) => {
reject(new Error('reject')); // manually rejecting to mimic actual code...
}).catch(error => {
// do some additional logging...
throw error;
});
};
it('should catch and re-throw error', () => {
// Received function did not throw
// and
// Unhandled promise rejection
expect(() => foo()).toThrow();
// Test passes, even when `throw error` is commented out with false positive
expect(foo()).rejects.toThrow();
});
I can successfully check that the logging function is called, but can't figure out how to ensure the error is re-thrown after.
WORKING UPDATE :)
thanks to #skyboyer & #Bergi for getting me to think about the issue a bit differently, and exposing me to some of the finer points of jest
Below is both the updated code to show the logging function, and the updated tests i settled on.
The issues that led to this were
unable to test logging was called due to the error being re-thrown
unable to test the value of the error being re-thrown
Catching the rejected promise allowed me to do both.
I was going to leave in the rejects.toEqual test, but it seems redundant now...
interested in any feedback! and thanks again!
// myModule.js
export const logging = () => {};
export const bar = () => new Promise(resolve => {});
export const foo = () => {
return bar().catch(error => {
logging();
throw error;
});
};
describe('myModule', () => {
let fooReturn;
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(myModule, 'bar').mockImplementation(() => {
return Promise.reject({ error: 'bar error' });
});
jest.spyOn(myModule, 'logging').mockImplementation(() => {});
fooReturn = myModule.foo();
});
it('should catch and re-throw error', () => {
expect.assertions(1);
fooReturn.catch(result => expect(result).toEqual({ error: 'bar error' }));
// removed since the above test covers that the promise was rejected
// return fooReturn.rejects.toEqual(expect.anything());
});
it('should call the loggin method', async () => {
expect.assertions(1);
// prevents UnhandledPromiseRejectionWarning
fooReturn.catch(() => {});
expect(myModule.logging).toBeCalled();
});
});
You missed return.
https://jestjs.io/docs/asynchronous#resolves--rejects
Be sure to return the assertion—if you omit this return statement, your test will complete before the promise returned from fetchData is resolved and then() has a chance to execute the callback.
Your test should be
it('should catch and re-throw error', () => {
return expect(foo()).rejects.toEqual(expect.anything());
});
As u/Bergi noticed with async/await it may look more laconic:
it('should catch and re-throw error', async () => {
await expect(foo()).rejects.toEqual(expect.anything());
});
but if we miss to add await before our expect we will have exact the same issue as in version 1 without return. So beware.
Testing errors and promises. I have a situation similar to the one below:
public myUtilityMethod(): Promise<string> {
// some synchronous stuff
console.log('bla bla');
// some asynchronous stuff
return Promise.resolve('ok');
}
public async doSomething(): Promise<void> {
let promise;
try {
promise = this.myUtilityMethod();
} catch (e) {
throw new MyError('I knew it', e, {});
}
await Promise.all([promise]);
return Promise.resolve();
}
I want to test, when something goes wrong in the synchronous part of myUtilityMethod, I throw a MyError, so I write the following test
it('should throw MyError when something goes wrong in the synchronous part of myUtilityMethod', fakeAsync(() => {
// given
const error = new Error('something went wrong');
component.myUtilityMethod = sinon.stub().throws(error);
// when
expect(() => {
component.doSomething();
flushMicrotasks();
}).toThrow(jasmine.any(MyError));
}));
The test fails because
Error: Expected function to throw <jasmine.any(MyError)>, but it threw Error: Uncaught (in promise): MyError: {"message":"i knew it","nativeError":{},"context":{}}.
Am I missing something obvious?
I think your assertion is not correct. You're mocking that doSomething to throw new Error('something went wrong') but you're expecting a MyError.
I would stub myUtilityMethod to throw the Error so then it goes in your catch block and the throw new MyError('I knew it', e, {}) to be traversed.
it('should throw MyError when something goes wrong in the synchronous part of myUtilityMethod', fakeAsync(() => {
// given
const error = new Error('something went wrong');
component.myUtilityMethod = sinon.stub().throws(error); // make myUtilityMethod throw the error
// when
expect(() => {
component.doSomething(); // once myUtilityMethod throws the error, doSomething's catch block will run
flushMicrotasks();
}).toThrow(new LabelGenerationError('I knew it', error, {}));
}));
Edit: After looking at your new error:
// change this line to back to how it was
.toThrow(jasmine.any(MyError));
// to this
.toThrow(new LabelGenerationError('I knew it', error, {}));
async functions wrap all their content in a promise, so even if the error is thrown before any "await" is encountered, what happens is that the promise rejects instead of throwing an error. The following test passes:
it('test test', (done) => {
const error = new Error('something went wrong');
component.myUtilityMethod = sinon.stub().throws(error);
component.doSomething().catch((e) => {
expect(e).toEqual(jasmine.any(MyError));
done();
});
});
I'm working on adding test coverage to a Node project I'm working on using Jest. The code I'm testing is throwing errors within promises resulting in an UnhandledPromiseRejectionWarning message being logged to the console.
While writing tests, I can pretty easily identify these issues and resolve them, but these warnings aren't actually causing Jest to mark the tests as failed, so our CI won't catch it. I've searched around for any suggestions and haven't found much.
I did find in Node's documentation that you can catch these warnings and handle them...
process.on('unhandledRejection', (error) => {
throw error; // Or whatever you like...
});
So it seems like it would be pretty straightforward to add this code into my test cases. After all, an Error thrown within the test should cause the test to fail...
describe('...', () => {
it('...', () => {
process.on('uncaughtRejection', (error) => {
throw error;
});
// the rest of my test goes here
});
});
Unfortunately the behavior I'm seeing is that the error does get thrown, but Jest doesn't catch it and fail the test. Instead, Jest crashes with this error and the tests don't continue to run. This isn't really desirable, and seems like incorrect behavior.
Throwing an error outside of the uncaughtRejection handler works as expected: Jest logs the thrown error and fails the test, but doesn't crash. (i.e. the test watcher keeps watching and running tests)
The way I've approached this is very much tied into the way I write my functions - basically, any function that uses promises should return a promise. This allows whatever code calls that function to handle catching errors in any way it sees fit. Note that this is my approach and I'm not going to claim this is the only way to do things.
For example... Imagine I'm testing this function:
const myFunction = () => {
return doSomethingWithAPromise()
.then(() => {
console.log('no problems!');
return true;
});
};
The test will look something like this:
describe('...', () => {
it('...', () => {
return myFunction()
.then((value) => {
expect(value).toBe(true);
});
});
});
Which works great. Now what happens if the promise is rejected? In my test, the rejected promise is passed back to Jest (because I'm returning the result of my function call) and Jest can report on it.
If, instead, your function does not return a promise, you might have to do something like this:
const myOtherFunction = () => {
doSomethingWithAPromise()
.then(() => {
console.log('no problems!');
return true;
})
.catch((err) => {
// throw the caught error here
throw err;
});
};
Unlike the example above, there is no (direct) way for Jest to handle a rejected promise because you're not passing the promise back to Jest. One way to avoid this might be to ensure there is a catch in the function to catch & throw the error, but I haven't tried it and I'm not sure if it would be any more reliable.
Include the following content in Jest's setupFiles:
if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
process.on('unhandledRejection', reason => {
throw reason
})
// Avoid memory leak by adding too many listeners
process.env.LISTENING_TO_UNHANDLED_REJECTION = true
}
Courtesy of stipsan in https://github.com/facebook/jest/issues/3251#issuecomment-299183885.
module:
export function myPromise() {
return new Promise((resolve, reject) => {
const error = new Error('error test');
reject(error);
});
}
test:
import { myPromise } from './module';
it('should reject the promise', () => {
expect.assertions(1);
const expectedError = new Error('error test');
myPromise().catch((error) => {
expect(error).toBe(expectedError);
});
From the node documentation site we can see that The process object is an instance of EventEmitter.
Using the emit function from process we can trigger the errors like uncaughtRejection and uncaughtException programmatically when needed.
it("should log the error", () => {
process.emit("unhandledRejection");
...
const loggerInfo = jest.spyOn(logger, "info");
expect(loggerInfo).toHaveBeenCalled();
});
Not sure if this helps, but you can also assert for promise rejections as such
index.js
module.exports = () => {
return Promise.reject('it didnt work');
}
index.spec.js
const thing = require('../src/index');
describe('rejected promise', () => {
it('should reject with a reason', ()=> {
return expect(thing()).rejects.toEqual('it didnt work');
});
});
The following test is behaving oddly:
it('Should return the exchange rates for btc_ltc', function(done) {
var pair = 'btc_ltc';
shapeshift.getRate(pair)
.then(function(data){
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
done();
})
.catch(function(err){
//this should really be `.catch` for a failed request, but
//instead it looks like chai is picking this up when a test fails
done(err);
})
});
How should I properly handle a rejected promise (and test it)?
How should I properly handle a failed test (ie: expect(data.rate).to.have.length(400);?
Here is the implementation I'm testing:
var requestp = require('request-promise');
var shapeshift = module.exports = {};
var url = 'http://shapeshift.io';
shapeshift.getRate = function(pair){
return requestp({
url: url + '/rate/' + pair,
json: true
});
};
The easiest thing to do would be to use the built in promises support Mocha has in recent versions:
it('Should return the exchange rates for btc_ltc', function() { // no done
var pair = 'btc_ltc';
// note the return
return shapeshift.getRate(pair).then(function(data){
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
});// no catch, it'll figure it out since the promise is rejected
});
Or with modern Node and async/await:
it('Should return the exchange rates for btc_ltc', async () => { // no done
const pair = 'btc_ltc';
const data = await shapeshift.getRate(pair);
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
});
Since this approach is promises end to end it is easier to test and you won't have to think about the strange cases you're thinking about like the odd done() calls everywhere.
This is an advantage Mocha has over other libraries like Jasmine at the moment. You might also want to check Chai As Promised which would make it even easier (no .then) but personally I prefer the clarity and simplicity of the current version
As already pointed out here, the newer versions of Mocha are already Promise-aware. But since the OP asked specifically about Chai, it's only fair to point out the chai-as-promised package which provides a clean syntax for testing promises:
using chai-as-promised
Here's how you can use chai-as-promised to test both resolve and reject cases for a Promise:
var chai = require('chai');
var expect = chai.expect;
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
...
it('resolves as promised', function() {
return expect(Promise.resolve('woof')).to.eventually.equal('woof');
});
it('rejects as promised', function() {
return expect(Promise.reject('caw')).to.be.rejectedWith('caw');
});
without chai-as-promised
To make it really clear as to what's getting tested, here's the same example coded without chai-as-promised:
it('resolves as promised', function() {
return Promise.resolve("woof")
.then(function(m) { expect(m).to.equal('woof'); })
.catch(function(m) { throw new Error('was not supposed to fail'); })
;
});
it('rejects as promised', function() {
return Promise.reject("caw")
.then(function(m) { throw new Error('was not supposed to succeed'); })
.catch(function(m) { expect(m).to.equal('caw'); })
;
});
Here's my take:
using async/await
not needing extra chai modules
avoiding the catch issue, #TheCrazyProgrammer pointed out above
A delayed promise function, that fails, if given a delay of 0:
const timeoutPromise = (time) => {
return new Promise((resolve, reject) => {
if (time === 0)
reject({ 'message': 'invalid time 0' })
setTimeout(() => resolve('done', time))
})
}
// ↓ ↓ ↓
it('promise selftest', async () => {
// positive test
let r = await timeoutPromise(500)
assert.equal(r, 'done')
// negative test
try {
await timeoutPromise(0)
// a failing assert here is a bad idea, since it would lead into the catch clause…
} catch (err) {
// optional, check for specific error (or error.type, error. message to contain …)
assert.deepEqual(err, { 'message': 'invalid time 0' })
return // this is important
}
assert.isOk(false, 'timeOut must throw')
log('last')
})
Positive test is rather simple. Unexpected failure (simulate by 500→0) will fail the test automatically, as rejected promise escalates.
Negative test uses the try-catch-idea. However: 'complaining' about an undesired pass happens only after the catch clause (that way, it does not end up in the catch() clause, triggering further but misleading errors.
For this strategy to work, one must return the test from the catch clause. If you want't to test anything else, use another it()-block.
Thre is a better solution. Just return the error with done in a catch block.
// ...
it('fail', (done) => {
// any async call that will return a Promise
ajaxJson({})
.then((req) => {
expect(1).to.equal(11); //this will throw a error
done(); //this will resove the test if there is no error
}).catch((e) => {
done(e); //this will catch the thrown error
});
});
this test will fail with following message: AssertionError: expected 1 to equal 11
I'm writing a test for a function, and have to trigger the .catch part of that function, but Jasmine's spiesfor some reason can't do that.
Method to be tested:
foo(){
doStuff()
.catch((error) => {
//stuff I still have to test
bar()
})
}
doStuff() returns a Promise (hence the .catch-Setup), but for this test it's supposed to throw an error.
Here is my test:
it('tests the error handling of foo',(done) =>{
spyOn(object,'foo').and.throwError('Test Error');
object.foo();
expect(object.bar).toHaveBeenCalled();
done();
});
Is the way I'm approaching this wrong? Is that an error with Jasmine? (Google didn't find anything)
[I stick to the (done) setup because almost all other tests are async and I want to keep the style]
[I cannot change the code to be tested]
I think I had a problem similar to yours. Here's how I solved it
import { throwError } from 'rxjs';
it(`...`, fakeAsync(() => {
spy = spyOn(authService, 'signIn').and.returnValue(throwError(loginError));
/* do things */
expectSnackbar('error', loginError);
expect(authService.ensureLogin).toHaveBeenCalled();
}));
Here's how the call to the signIn method looks like:
return this.authService
.signIn(payload.email, payload.password)
.map((userId: string) => {
// Whatever
})
.catch(error => {
// Do something with the error
});
And how the thrown error looks like inside signIn
public signIn(email: string, password: string): Observable<string> {
return this.jsonServerService.get('login').pipe(
map(users => {
if (/* valid */) {
return user.userId;
} else {
throw new Error('Error');
}
}),
);
}
If you call and.throwError(...); you throw the error within the test method.
You can try to return a Promise rejection instead:
spyOn(object, 'foo').and.rejectWith(new Error('Test Error'));
and.throwError does not exist when I try it. Maybe I'm using an old version of jasmine.
I got it to work by returning a promise rejection:
and.returnValue(Promise.reject({response: {status: 401}}))