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();
});
});
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.
I'd like to catch all my exceptions in one place, but I can't do that currently:
There is an important thing to note if you like more try/catch. The following code won't catch the error:
[...]
Remember: a rejected Promise will propagate up in the stack unless you catch it. To catch the error properly in try/catch you would refactor like so:
whatever().catch(err => console.error(err));
But here is my code as I would like to have it:
async function getMtgJsonVersion() {
try {
const response = await axios(metaUrl).catch((err) => { throw err; });
const { data: { meta: { version } } } = response;
return version;
} catch (error) {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
}
}
and my test:
// console.error is mocked higher up in the test file
it.only('handles errors in fetching', async () => {
expect.assertions(2);
axios.mockReset(); // I use mockImplementationOnce elsewhere
axios.mockRejectedValueOnce(new Error('bang'));
expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
});
But when I run it, I get that the last expect is not fulfilled?
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: StringContaining "bang"
Number of calls: 0
I was hoping to catch all my throws in one place, but it looks like it's not as simple as I thought.
Is this possible, and how?
Because expect(fn).rejects.* is an asynchronous action, then it will take "a little time" to finish.
In your code, expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang')) will run before expect(() => getMtgJsonVersion()).rejects.toThrow('bang'); line. At that time, the console.log is not be called yet.
To make it work as your expectation, you have to wait until getMtgJsonVersion finishes, then assert on the log function. rejects.toThrow('bang') return a promise, then just wait for it with await keyword:
await expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
My note: Avoid using try/catch in "child" unit, just use it in the "final parent" function, if you just want to log when the http request is failed:
async function getMtgJsonVersion() {
const { data } = await axios(metaUrl).catch((error) => {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
});
return data.meta.version.
}
I have this code
const writeToDB = async (data) => {
console.log("Inside db put")
try {
const resp = await dynamoDB.put(data).promise();
console.log("Data added db: ", resp);
return "successfully inserted"
} catch (err){
throw new Error(`Failed to write in database`, err)
}
}
I have 2 tests for this functionality one to check when its sucessful and one where it throws an error.
When I run stryker I get a surviving mutation
- } catch (err){
- throw new Error(`Failed to write in database`, err)
- }
+ } catch (err){}
I believe this is trying to find a test "if it catches the error but does not throw the error".
How do I write a test to kill this particular Blockstatement mutation. The code is always going to throw the error that I have specified.
The mutation test is entirely correct. The approach
return mm.putMetadataItem(metadata).catch(err => {
assert.throws(() => {
throw error
}, err)
})
to check for the expected behaviour of the putMetadataItem function is wrong. In particular, if the returned promise is not rejected, the .catch() callback with your assertion doesn't run at all, and the fulfilled promise is returned, which causes the unit test to pass. Also assert.throws is rather pointless here, you know for sure that throw error will throw, so all this does is to check equality between error and err.
You would need to write
return mm.putMetadataItem(metadata).then(() => {
throw new AssertionError('expected putMetadataItem() to reject');
}, err => {
assert.equal(error, new Error('Failed to write in database'));
});
however you actually should use assert.rejects:
return assert.rejects(() => {
return mm.putMetadataItem(metadata);
}, new Error('Failed to write in database'));
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.
I found the following code in a tutorial:
promise.then(function(result){
//some code
}).catch(function(error) {
throw(error);
});
I'm a bit confused: does the catch call accomplish anything? It seems to me that it doesn't have any effect, since it simply throws the same error that was caught. I base this on how a regular try/catch works.
There is no point to a naked catch and throw as you show. It does not do anything useful except add code and slow execution. So, if you're going to .catch() and rethrow, there should be something you want to do in the .catch(), otherwise you should just remove the .catch() entirely.
The usual point for that general structure is when you want to execute something in the .catch() such as log the error or clean up some state (like close files), but you want the promise chain to continue as rejected.
promise.then(function(result){
//some code
}).catch(function(error) {
// log and rethrow
console.log(error);
throw error;
});
In a tutorial, it may be there just to show people where they can catch errors or to teach the concept of handling the error, then rethrowing it.
Some of the useful reasons for catching and rethrowing are as follows:
You want to log the error, but keep the promise chain as rejected.
You want to turn the error into some other error (often for easier error processing at the end of the chain). In this case, you would rethrow a different error.
You want to do a bunch of processing before the promise chain continues (such as close/free resources) but you want the promise chain to stay rejected.
You want a spot to place a breakpoint for the debugger at this point in the promise chain if there's a failure.
You want to handle a specific error or set of errors, but rethrow others so that they propagate back to the caller.
But, a plain catch and rethrow of the same error with no other code in the catch handler doesn't do anything useful for normal running of the code.
Both .then() and .catch() methods return Promises, and if you throw an Exception in either handler, the returned promise is rejected and the Exception will be caught in the next reject handler.
In the following code, we throw an exception in the first .catch(), which is caught in the second .catch() :
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
throw new Error('Something failed');
console.log('Do this'); // Never reached
})
.catch(() => {
console.log('Something failed');
throw new Error('Something failed again');
})
.catch((error) => {
console.log('Final error : ', error.message);
});
The second .catch() returns a Promised that is fulfilled, the .then() handler can be called :
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
throw new Error('Something failed');
console.log('Do this'); // Never reached
})
.catch(() => {
console.log('Something failed');
throw new Error('Something failed again');
})
.catch((error) => {
console.log('Final error : ', error.message);
})
.then(() => {
console.log('Show this message whatever happened before');
});
Useful reference : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#Chaining_after_a_catch
Hope this helps!
There is no important difference if you leave out the catch method call completely.
The only thing it adds is an extra microtask, which in practice means you'll notice the rejection of the promise later than is the case for a promise that fails without the catch clause.
The next snippet demonstrates this:
var p;
// Case 1: with catch
p = Promise.reject('my error 1')
.catch(function(error) {
throw(error);
});
p.catch( error => console.log(error) );
// Case 2: without catch
p = Promise.reject('my error 2');
p.catch( error => console.log(error) );
Note how the second rejection is reported before the first. That is about the only difference.
So it sounds like your question is, "In the promise chain, what does the .catch() method do?"
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
The throw statement "will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate."
In the promise chain, the .then() method will return some type of data chunk. This return of the chunk will complete the promise. The successful return of the data completes the promise. You can think of the .catch() method in the same way. .catch() however will handle unsuccessful data retrieves. The throw statement completes the promise. Occasionally, you will see developers use .catch((err) => {console.log(err))} which would also complete the promise chain.
You actually don't need to re throw it, just leave the Promise.catch empty otherwise it will consider as un handle the reject and then wrap the code in a try catch and it will catch the error automatically which is passing down.
try{
promise.then(function(result){
//some code
}).catch(function(error) {
//no need for re throwing or any coding. but leave this as this otherwise it will consider as un handled
});
}catch(e){
console.log(e);
//error can handle in here
}
In the promise chain, it is better to use .catch
ex in function f2: .then(...).catch(e => reject(e));
test1 - with try catch
test2 - without try or .catch
test3 - with .catch
function f1() {
return new Promise((resolve, reject) => {
throw new Error('test');
});
}
function f2() {
return new Promise((resolve, reject) => {
f1().then(value => {
console.log('f1 ok ???');
}).catch(e => reject(e));
});
}
function test1() {
console.log('test1 - with try catch - look in F12');
try {
f2().then(() => { // Uncaught (in promise) Error: test
console.log('???'); });
} catch (e) {
console.log('this error dont catched');
}
}
function test2() {
console.log('test2 - without try or .catch - look in F12');
f2(); // Uncaught (in promise) Error: test
}
function test3() {
console.log('test3 - with .catch');
f2().then(value => {
console.log('??');
}).catch(e => {
console.log(' now its ok, error ', e);
})
}
setTimeout(() => { test1();
setTimeout(() => { test2();
setTimeout(() => { test3();
}, 100);
}, 100);
}, 100);