0

I need to test function testMe using Mocha. But there is trouble when my unit test throw an error. Here is simpified example

function testMe(callback) {
    new Promise((resolve, reject) => {
        setTimeout(() => resolve([1,2,3]), 1000);
    }).then((result) => {
        callback(null, result);
    }).catch((error) => {
        callback(error, null)
    });
}

testMe((err, result) => {
   if(err) throw new Error();
   if(result.length < 5) throw new Error();
});

In this example after throw runs catch block. But I need to run catch block only after reject.

EDIT:

In this case the script never stop. I don't understand why.

function testMe(callback) {
    new Promise((resolve, reject) => {
        setTimeout(() => resolve([1,2,3]), 1000);
    }).then((result) => {
        callback(null, result);
    }, (error) => {
        callback(error, null)
    }).catch(() => {
        console.log('Do not throw an error but still running');
    });
}

testMe((err, result) => {
   if(err) throw new Error();
   if(result.length < 5) throw new Error();
});
Игорь
  • 158
  • 1
  • 2
  • 11
  • Don't use callback parameters when working with promises! – Bergi Apr 10 '17 at 08:48
  • But I need to use it. Because inside testMe I'm using horseman API. – Игорь Apr 10 '17 at 08:49
  • [Use `.then(…, …)` instead of `.then(…).catch(…)`](http://stackoverflow.com/q/24662289/1048572) to avoid calling the `callback` twice, though that still won't get you an exception; only an unhandled rejection. – Bergi Apr 10 '17 at 08:49
  • No, you don't need to pass a callback to `testMe` - you should have `testMe` *return* a promise, and then check that with mocha. – Bergi Apr 10 '17 at 08:51
  • If I use .then(..., ...) instead of .then().catch() I receive UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error – Игорь Apr 10 '17 at 08:52
  • There is a lot of functions that receive callbacks with common interface. But one of them need to use promise inside. – Игорь Apr 10 '17 at 08:54
  • The proper course of action would be to promisify all those functions of course :-) But still, you should not (cannot) `throw` in asynchronous test cases, you need to call Mocha's `done` callback with the error. (Or maybe Mocha supports promise return values, I don't know) – Bergi Apr 10 '17 at 18:46
  • Bergi, I don't throw myself. Mocha throws inside expect statement. – Игорь Apr 10 '17 at 18:48
  • Then `catch` that and call mocha's asynchronous callback, or make mocha aware that you are using promises. – Bergi Apr 10 '17 at 18:52
  • Yes, I am using first option you promote. And there is problem. – Игорь Apr 10 '17 at 18:55
  • Where is the [asynchronous `done` callback](https://mochajs.org/#asynchronous-code) in your code? Or why don't you [`return` the promise](https://mochajs.org/#working-with-promises)? – Bergi Apr 10 '17 at 18:57
  • Done callback should be at the end of testMe callback handler. – Игорь Apr 10 '17 at 19:01
  • I cannot change unit tests in this case. – Игорь Apr 10 '17 at 19:01
  • The unit test you currently have will simply not work properly with asynchronous code. You *should* change it, promises or not. – Bergi Apr 10 '17 at 19:09
  • Current generic unit tests works with callbacks correctly, but the problem is using a promise inside one of functions. – Игорь Apr 10 '17 at 19:12
  • Well, the proper fix is to make the tests use promises. But if you absolutely want to go back from your promise function to callbacks, you can [use `.done`](http://stackoverflow.com/questions/26667598/will-javascript-es6-promise-support-done-api). Are you using a particular promise library? – Bergi Apr 10 '17 at 19:20
  • I'm using native ES6 promises – Игорь Apr 10 '17 at 19:49
  • Then you can only use that `setTimeout` hack I linked. – Bergi Apr 10 '17 at 19:54
  • @Bergi Thanks! It's interesting and I will try tomorrow. – Игорь Apr 10 '17 at 19:56
  • @Bergi Thanks, it seems like correct solution. Can you post an answer? – Игорь Apr 11 '17 at 16:19

2 Answers2

2

When you work with promises then return the promises from functions instead of taking callbacks.

For example, instead of:

function testMe(callback) {
    new Promise((resolve, reject) => {
        // ...
    });
}

use:

function testMe(callback) {
    return new Promise((resolve, reject) => {
        // ...
    });
}

that way you will have the promise available to the caller of the function.

If you need to mix both styles, i.e. returning promises and taking callbacks, consider using a reliable library to handle that for you especially if you have trouble coding the translation between those style yourself:

rsp
  • 107,747
  • 29
  • 201
  • 177
1

You can simply return the promise from the test:

function testMe() {
//             ^^ drop the callback
    return new Promise((resolve, reject) => {
//  ^^^^^^ return the promise
        setTimeout(() => resolve([1,2,3]), 1000);
    });
}

var p = testMe().then(result) => {
//              ^^^^^ use the promise
   if(result.length < 5) throw new Error();
});
return p; // to mocha
Bergi
  • 630,263
  • 148
  • 957
  • 1,375