I had hard times testing promise based functions. I am using Mocha as test framework and chai library for assertion framework. I new to both Mocha and chai. I had few problems and I do not know that is wrong with my code.Maybe my testing approach totally wrong, maybe someone help em.
I get Uncaught (in promise) error for expect, but actually I do not know whether my way is the correct way of testing these functions.
Here is my returnResult function which resolves a value -> a string
var returnResult = function (stateParamName, stateParamValue) {
return new Promise((resolve, reject) => {
peer3.get({ path: { equals: 'todo/#0' } }).then(function (results) {
console.log(stateParamName + ': ' + results[0].value[stateParamName])
resolve(results[0].value[stateParamName]);
});
});
}
And here is my mocha test
describe("Call Tests Suite", function () {
describe("Basic Call Test", function () {
it("Call status for both side should be IN CALL", function () {
peer3.call('login/add', ['testaccount1'])
.then(() => peer3.call('todo/makeCall1', ['testaccount2#testdomain.com']))
.then(() => checkResult('state_term', 'RINGING'))
.then((interval) => { clearInterval(interval); peer3.call('todo/answerCall2', ['empty']) })
.then(() => checkResult('state_term', 'IN_CALL'))
.then((interval) => clearInterval(interval))
//.then(console.log('test sonucu: ' + returnResult('state_term', 'IN_CALL')))
.then(returnResult('state_term', 'IN_CALL'))
.then((result) => expect(result).to.equal('IN_CALL'))
});
});
As you see I am only using assert for last result. Maybe I should test whole test as an promise function. Can someone help me on this ?
I don't know from where your error come from. However, there are a lot of things in your code you can improve, and may lead you to a better debugging, so you can find the error:
1- You should add a .catch handler at the end of the promise chain. The 'uncaught error' refers to this: you have an error in one of your then handlers but it is not caught in a catch. You should add a catch call at the end of the train:
describe("Call Tests Suite", function () {
describe("Basic Call Test", function () {
it("Call status for both side should be IN CALL", function () {
peer3.call('login/add', ['testaccount1'])
.then(() => peer3.call('todo/makeCall1', ['testaccount2#testdomain.com']))
.then(() => checkResult('state_term', 'RINGING'))
.then((interval) => { clearInterval(interval); peer3.call('todo/answerCall2', ['empty']) })
.then(() => checkResult('state_term', 'IN_CALL'))
.then((interval) => clearInterval(interval))
//.then(console.log('test sonucu: ' + returnResult('state_term', 'IN_CALL')))
.then(returnResult('state_term', 'IN_CALL'))
.then((result) => expect(result).to.equal('IN_CALL'))
.catch(err => {
console.log(err);//This will allow you better debugging.
})
});
});
Ok, now, we have to keep in mind that your code is asynchronous. However, mocha it functions, by default, are synchronous: they will not wait for your async code to execute.
In order to tell mocha that your test is asynchronous, you have to pass a parameter to the test. This parameter is a function, usually called done, that you must explicitly call when your test finish. Otherwise, your test will finish before reaching the asynchronous part of your code, usually giving you false positives.
describe("Call Tests Suite", function () {
describe("Basic Call Test", function () {
it("Call status for both side should be IN CALL", function (done) {
peer3.call('login/add', ['testaccount1'])
.then(() => peer3.call('todo/makeCall1', ['testaccount2#testdomain.com']))
.then(() => checkResult('state_term', 'RINGING'))
.then((interval) => { clearInterval(interval); peer3.call('todo/answerCall2', ['empty']) })
.then(() => checkResult('state_term', 'IN_CALL'))
.then((interval) => clearInterval(interval))
//.then(console.log('test sonucu: ' + returnResult('state_term', 'IN_CALL')))
.then(returnResult('state_term', 'IN_CALL'))
.then((result) => expect(result).to.equal('IN_CALL'))
.then( () => {
done(); //dont use .then(done) or things may break due to extra
parameter
})
.catch( err => {
console.log(err);
done(err); //passing a parameter to done makes the test fail.
})
});
});
Still, there are an issue with your code we must address. The then method expects a function as a parameter. However, in this line:
.then(returnResult('state_term', 'IN_CALL'))
You are passing it a call to returnResult('state_term', 'IN_CALL'), which return not a function, but a promise. You should pass a function instead -and then return the promise-:
.then(() => returnResult('state_term', 'IN_CALL')) //now the parameter to
//then is a function that return a Promise, not a Promise itself.
Also, as you've been told in the commments, you're explicitly returning a new Promise that wraps a Promise in your return result function: that is not needed at all and that function could be written as so:
var returnResult = function (stateParamName, stateParamValue) {
return peer3.get({ path: { equals: 'todo/#0' } }).then(function (results) {
console.log(stateParamName + ': ' + results[0].value[stateParamName]);
return(results[0].value([stateParamName]));
});
}
However, there are a lot of things in your code that seems really odd (I'am not sure at all why the setinterval calls are there), so I'm pretty confident the test will not work as you expect. You should start by familiarizing yourself with Promises and asynchronous mocha tests, and don't try to test really long sequences of asynchronous operations. good luck!
Related
This is a design question that came up to me while unit testing.
Let's dive into the example:
Imagine this:
async function foo() {
try {
return apiCall()
}
catch (e) {
throw new CustomError(e);
}
}
async function bar() {
return foo()
}
async function main() {
try {
await bar()
}catch(e) {
console.error(e)
}
}
main()
What do we see here? That the only function that hasn't got a try-catch block is bar.
But if foo fails, it should get catched by the main catch.
While unittesting this like
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => console.log(result))
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})
The output we see is that an Unhandled promise rejection is logged in the console.
So, my question is... even if I know that the main() will catch the error, should I use try-catch block inside all async functions?
try..catch may be necessary if a function is able to recover from an error, do a side effect like logging, or re-throw a more meaningful error.
If CustomError is more preferable than an error that apiCall can throw then try..catch necessary, otherwise it doesn't. Also the problem with foo is that it handles only synchronous errors. In order to handle rejected promises, it should be return await apiCall(), this is a known pitfall of async.
Uncaught rejections are unwanted, they currently result in UnhandledPromiseRejectionWarning and are expected to crash Node in future versions. It's preferable to handle an error in a meaningful way at top level, so main needs to catch the error. This can be delegated to process uncaughtRejection event handler but it may be beneficial for it to stay extra level of error handling that should be never reached.
The output we see is that an Unhandled promise rejection is logged in the console.
This shouldn't happen. A rejection needs to be handled by the test. One possible point of failure is explained above, foo can return original error from apiCall instead of CustomError in case it wasn't correctly mocked, this will fail the expectation and result in unhandled rejection in catch(). Another point of failure is that the test has unchained promise because it wasn't returned, the test always passes.
Asynchronous test that uses promises should always return a promise. This can be improved by using async..await. foo is async, it's expected to always return a promise:
it('foo should throw', async () => {
foo.mockImplementantion(() => { return Promise.reject(new CustomError('error')) });
await expect(bar()).rejects.toThrow(CustomError);
})
Now even if foo mock fails (foo mock won't affect bar if they are defined in the same module as shown) and bar rejects with something that is not CustomError, this will be asserted.
No. You don't need to use try/catch in every async/await. You only need to do it at the top level. In this case your main function which you are already doing.
Weather you should is a matter of opinion. The go language designers feel strongly enough about this that is has become the standard in go to always handle errors at each function call. But this is not the norm in javascript or most other languages.
Unhandled promise rejection
Your unhandled promise rejection is thrown by your it() function because you are not telling it to wait for the promise to complete.
I assume you are using something like mocha for the unit test (other frameworks may work differently). In mocha there are two ways to handle asynchronous tests:
Call the done callback - the it() function will always be called with a done callback. It is up to you weather you want to use it or like in your posted code to not use it:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => {
console.log(result);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
})
})
Return a Promise. If you return a promise to the it() function mocha will be aware that your code is asynchronous and wait for completion:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar() // <----------- THIS WOULD ALSO FIX IT
.then((result) => {
console.log(result);
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
})
})
})
In short, there is nothing wrong with your code. But you have a bug in your unit test.
As #Bergi told me I will post some solutions right here
I wrap the function in a try catch block
1.
async function bar() {
try{
return foo()
} catch (e) {
throw e
}
}
Rewrite the test
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => { throw result }) // this is because we are expecting an error, so if the promise resolves it's actually a bad sign.
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
done();
})
})
Use return in the test case
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar()
.then((result) => { throw result })
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})
I have a suite of tests but something is just not clicking regarding callback assertions. My feeling is that the done() parameter needs to be woven in, but I'm doing it wrong.
I basically have two function structures, where the callback is either nested inside of a single then statements, or a then inside of another then:
function foo(cb) {
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
.catch(err => console.error(err))
}
and
function foo(cb) {
return fetch('foo.com')
.then(()=>cb())
.catch(err => console.error(err))
}
I'm looking to assert that the callback was called in both cases.
I have tried
describe('ugh why can't I figure this out'?, () => {
it('is confusing', (done) => {
const testCb = jest.fn()
foo(testCb);
expect(testCb).toHaveBeenCalled()
done();
//failed: expected testCb to be called, but it was not called
}
})
I'm not sure how to move forward- I'm not a fan of the spaghetti on the wall approach, so I'd rather understand how to implement jest for testing async code before I just start switching in different syntax. The above code seems like it should work because the callback is part of the function execution, so if the higher order function executes, the callback would necessarily be executed, and should have been "called". Obviously this reasoning is wrong since the test isn't passing, but I'm not quite seeing the way around this.
Thanks very much for any direction/insight :).
This jest example seems to match mine- what am I missing?
describe('drinkAll', () => {
test('drinks something lemon-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'lemon');
expect(drink).toHaveBeenCalled();
});
test('does not drink something octopus-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'octopus');
expect(drink).not.toHaveBeenCalled();
});
});
You're expect is being called before the fetch comes back. Do this:
foo(testCb)
.then(_ => {
expect(testCb).toHaveBeenCalled();
done();
})
.catch(err => {
done.fail(err);
});
By chaining on to the Promise returned by foo we ensure the fetch has come back. Once you go async you have to stay that way, you can't mix sync and async code like you did in your posted code:
const testCb = jest.fn()
foo(testCb); // this can take an arbitrary amt of time
expect(testCb).toHaveBeenCalled() // but this happens immediately
done();
FWIW you can also change this
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
Into this:
return fetch('foo.com')
.then((res)=> res.json())
.then(cb)
.catch((err) => ...
The extra level of nesting promises is unnecessary and makes the code hard to read.
I am trying to test a method using Jest... The method should return Promise.reject() .
Here is the code I wrote:
test('testing Invalid Response Type', () => {
const client = new DataClient();
client.getSomeData().then(response => {
console.log("We got data: "+ response);
}).catch(e => {
console.log("in catch");
expect(e).toBeInstanceOf(IncorrectResponseTypeError);
});
expect.assertions(1);
});
When I run the test, it prints "in catch" but fails with this exception:
Expected one assertion to be called but received zero assertion calls.
console.log src/data/dataclient.test.js:25
in catch
● testing Invalid Response Type
expect.assertions(1)
Expected one assertion to be called but received zero assertion calls.
at extractExpectedAssertionsErrors (node_modules/expect/build/extract_expected_assertions_errors.js:37:19)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
I solved it by adding return statement before the block.
With return statement the function will wait for catch block to finish.. and hence expect will be executed..
test('testing Invalid Response Type', () => {
const client = new DataClient();
return client.getSomeData().then(response => {
console.log("We got data: "+ response);
}).catch(e => {
console.log("in catch");
expect(e).toBeInstanceOf(IncorrectResponseTypeError);
});
expect.assertions(1);
});
You need to wait for the promise to finish to check number of assertions (to reach the .catch block).
see jest's asynchronous tutorial, specially the async/await solution. Actually, their example is almost identical to your problem.
in your example, you would do:
test('testing Invalid Response Type', async () => { // <-- making your test async!
const client = new DataClient();
await client.getSomeData().then(response => { // <-- await for your function to finish
console.log("We got data: "+ response);
}).catch(e => {
console.log("in catch");
expect(e).toBeInstanceOf(IncorrectResponseTypeError);
});
expect.assertions(1);
});
Btw, the accepted solution also works, but not suitable to multiple tests of async code
I'm writing unit-tests for my function that fetches info from some REST API. I am using ramda Future type (source).
The following test works weird:
it('should return Maybe of Nothing', done => {
let response = {
status: 200,
json: () => {
return {
results: []
}
}
}
let fakeFetch = {
fetch: () => {
return new Promise((resolve, reject) => {
resolve(response)
})
}
}
// String -> Future Error Maybe
let result = Utils.fetchGiantBomb(faker.random.word(), fakeFetch.fetch);
result.fork(err => {
assert.fail(err, 'expected to return Maybe of Nothing');
done();
}, data => {
expect(Maybe.isJust(data)).to.be.true;
done();
})
})
data should be of type Maybe.Nothing. If I expect Maybe.isNothing the test passes, but I want to see what happens when the test fails, so I set it to Maybe.isJust, which return false. After looking at this for a while, I noticed that when the expect fail it jump up to the error handling (err callback), which then just stop executing any assertion (which result in a 2000ms timeout).
In the Future sources I saw that when the success callback fails, it executes the failure callback. How can I complete this test so it display that the data is not what I expect?
I think the problem is that, when your REST call fails, done() is never called.
Not sure if expect has a .catch method when it fails, but you can try to add
.catch(done);
at the end of your expect function.
Hope that helps.
Calling future.fork(errorHandler, successHandler) will currently ensure that any exceptions thrown in the successHandler will propagate through to the errorHandler.
One way around this (though perhaps not ideal as it is undocumented) is to call future._fork(errorHandler, successHandler) rather than future.fork where errors thrown in the successHandler will not be captured.
Alternatively, a number of test frameworks support passing an error to the done callback such as:
result.fork(err => {
done('Expected to return Maybe of Nothing: ' + err);
}, data => {
expect(Maybe.isJust(data)).to.be.true;
done();
})
I think Ramda shouldn't catch the exception there. But i don't know what's they are trying to do.
It look like you are using Mocha. It maybe good idea to convert your Future to Promise first, then observe the Promise. ie:
const futureToPromise = future => {
return new Promise((resolve, reject) => future.fork(reject, resolve))
}
it('should return Maybe of Nothing', () => {
let response = {
status: 200,
json: () => {
return {
results: []
}
}
}
let fakeFetch = {
fetch: () => {
return new Promise((resolve, reject) => {
resolve(response)
})
}
}
// String -> Future Error Maybe
let result = Utils.fetchGiantBomb(faker.random.word(), fakeFetch.fetch);
// return it because Mocha can handle this
return futureToPromise(result).then(data => {
expect(Maybe.isJust(data)).to.be.true;
}, () => {
// fail
assert.fail(err, 'expected to return Maybe of Nothing');
})
})
The motivation for this is to be able to catch all possible errors with the ending .catch, even ones that happen in initial synchronous code.
I want to start my promise chain like so:
const bbPromise = require('bluebird');
bbPromise.do(() => {
someTask(); // Could throw
return someVar.doSomeOtherTaskAsync();
})
.then((result) => {
// Do something with result
})
.catch((err) => {
console.log('err: ', err);
});
Is there a function that works like bbPromise.do function? bbPromise.resolve just gives me the whole lambda function that was passed in, unexecuted.
I know I could do something like this:
bbPromise.bind({}).then(() => {
someTask(); // Could throw
return someVar.doSomeOtherTaskAsync();
})
...
or even:
new bbPromise((resolve, reject) => {
someTask(); // Could throw
return someVar.doSomeOtherTaskAsync().then(resolve).catch(reject);
})
...
But these are a little indirect. Is there a good way to start a promise chain by just executing a function that could return a promise, and that has errors caught in the ending .catch?
It sounds like you're looking for Promise.try. Promise.resolve is appropriate when you have a value already and want to build a promise chain from it, but if you want to run some code that may throw, use Promise.try instead.
try takes and executes a function, dealing with any synchronous exception in the same manner as then would. You would end up with something like:
bbPromise.try(someTask).then(() => {
return someVar.doSomeOtherTaskAsync();
}).then((result) => {
// Do something with result
}).catch((err) => {
console.log('err: ', err);
});