Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocha: assertions within promise not working

So, I'm completely new to mocha, and promises and I was asked to write integration tests, which posts form data to a page, gets an authentication key, and then run tests on the key.

I'm getting the key back properly, which is confirmed by my log statement, but when I run mocha, my test says 0 passing. It doesn't even print out the descriptions for the 'describe' and 'it' statements, implying that they do not work within the promise. Is there away that I can this work, while still giving the assertions access to my authorization key?

require('should');
require('sails');
var Promise = require("promise");
var request = require('request');

describe('Domain Model', function(){
  var options = { url:'link',
    form:{
      username: 'xxxxx@xxxxxx',
      password: '0000'
    },
    timeout: 2000
  };
  //request auth key
  var promise = new Promise(function(resolve,reject){
    request.post(options, function(error, response, body){
      //body contains the auth key, within a string of JSON
      resolve(body);
    });
  });
  //this is where I'm getting lost
  promise.then(function(data,test){
    var token = (JSON.parse(data))["access_token"];
    console.log(token);
      describe('Initialize',function(){
        it('should be an string', function(){
          (token).should.be.type('string');
        });

      });
  });

});
like image 205
I11umin8 Avatar asked May 27 '14 17:05

I11umin8


People also ask

How do you test your promises in Mocha?

Create a file named teams. js, import the API using require and create the function getTeamByPlayer it will return a promise, using setTimeout simulate the async process. Our promise return the resolve if the team with the player is found or the reject with an error if not found. Export the function to be used.

Is Mocha a BDD?

Mocha is a testing library for Node. js, created to be a simple, extensible, and fast. It's used for unit and integration testing, and it's a great candidate for BDD (Behavior Driven Development).

What is assertion in Mocha?

Usually, to perform automated tests, you'll need a testing library. Mocha is one such library. You can test one or more methods in your JavaScript code with Mocha. Assertions are a key part of writing automated tests with a tool like Mocha. They do the job of verifying that a piece of code returns the expected result.

How does Mocha JavaScript work?

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases.


1 Answers

Mocha is not able to find your tests because you're not structuring them correctly. The it() blocks should go immediately within the describe() blocks (and the describe() blocks may be nested within each other as you see fit). If you want to use promises, the promises should go inside the it() blocks, not the other way around. So, as a first pass, the updated code should look something like this (I'm cutting some parts out for brevity):

describe('Domain Model', function(){
  var options = { ... };

  describe('Initialize',function(){
    it('should be an string', function(){
      //request auth key
      var promise = new Promise(function(resolve,reject){
        // ...
      });
      promise.then(function(data,test){
        var token = (JSON.parse(data))["access_token"];
        console.log(token);
        (token).should.be.type('string');
      });
    });
  });
});

Note that the promise is now contained inside the body of the it(). Mocha should now be able to find your test. However, it won't pass. Why not?

As you probably know, promises are asynchronous. Testing asynchronous code using mocha requires using the done callback - see the guide for more info. Basically, this means the it() block should accept a parameter named done (the name is important!). This done thingy is something that mocha passes in automatically - its presence indicates to mocha that this test contains asynchronous code, and that therefore it has not completed executing until you say so. The way you indicate that your test is done is by executing done, because done is in fact a callback function. You should execute done() at whatever point your test is deemed complete - i.e., at the end of whatever code block is supposed to run last, which in your case is the bottom of the .then() handler for the promise. So, improving upon our last version, the code now looks like this (again, cutting some parts for brevity):

describe('Domain Model', function(){
  var options = { ... };

  describe('Initialize',function(){
    it('should be an string', function(done){
      //request auth key
      var promise = new Promise(function(resolve,reject){
        // ...
      });
      promise.then(function(data,test){
        var token = (JSON.parse(data))["access_token"];
        console.log(token);
        (token).should.be.type('string');
        done();
      });
    });
  });
});

Note that I just added the done parameter to it(), and then I called it at the bottom of the then().

At this point, the code should work, I think... I'm not sure as I'm not able to test it. However, there are a couple more things we could do to improve upon this further.

First, I question your use of promises here. If you had an API for obtaining the access token, then I would opt to have that API return a promise because promises are very convenient for the caller. However, as I'm sure you've noticed, constructing promises can be a bit tedious, and I don't think it adds much value for your code. I would opt to just do this:

describe('Domain Model', function(){
  var options = { ... };

  describe('Initialize',function(){
    it('should be an string', function(done){
      //request auth key
      request.post(options, function(error, response, body){
        //body contains the auth key, within a string of JSON
        var token = (JSON.parse(body))["access_token"];
        console.log(token);
        (token).should.be.type('string');
        done();
      });
    });
  });
});

Isn't that so much shorter and sweeter? The code is still asynchronous, so you should still make sure your it() block accepts a done callback, and you should call it when your test has finished.

Now, if you still insist on using promises, then there's one more thing I have to warn you about. What happens if there's an error inside of your .then() handler code? Well, according to the documentation:

If the handler that is called throws an exception then the promise returned by .then is rejected with that exception.

Do you have a rejection handler for your promise? No. So what does that mean? That means the error will get swallowed up silently. Your test will fail with Error: timeout of 2000ms exceeded, which is because the done handler never got called, but the actual cause of the error won't be shown, and you'll be pulling your hair out trying to figure out what went wrong.

So what can you do? You could use the second parameter to .then() to specify a rejection handler, and in there, you could take advantage of the fact that the done callback that mocha passes in to your test accepts an error argument, so if you call done("something"), your test will fail (which is what we want in this case), and the "something" will be the reason. So here's what that will look like in your case:

describe('Domain Model', function(){
  var options = { ... };

  describe('Initialize',function(){
    it('should be an string', function(done){
      //request auth key
      var promise = new Promise(function(resolve,reject){
        // ...
      });
      promise.then(function(data){
        var token = (JSON.parse(data))["access_token"];
        console.log(token);
        (token).should.be.type('string');
        done();
      }, function (err) {
        done(err);
      });
    });
  });
});

However, we can do even better. Consider what happens if an error gets thrown from within the rejection handler. Not likely, since you're not doing a whole lot in there - just calling done(err). But what if it does happen? Well, pretty much the same thing - the error will get silently swallowed up, the test will fail with a non-specific timeout error, and you'll be pulling out your hair again. Is there a way we can get that error to bubble up and get rethrown?

As a matter of fact, there is: Both Q and the promise library that you're using have an alternate handler called .done() (not to be confused with mocha's done callback). It's similar to .then(), but with a slightly different behavior around uncaught exceptions. From the documentation:

Promise#done(onFulfilled, onRejected)

The same semantics as .then except that it does not return a promise and any exceptions are re-thrown so that they can be logged (crashing the application in non-browser environments)

Perfect, that's exactly what we want. Just be sure to understand when you should use .then(), vs when you should use .done(). The Q API does a fantastic job of explaining (and the promise library you're using has similar behavior - I tested):

The Golden Rule of done vs. then usage is: either return your promise to someone else, or if the chain ends with you, call done to terminate it. Terminating with catch is not sufficient because the catch handler may itself throw an error.

(Note: .catch() appears to be Q-specific, but it's pretty similar to the onRejected callback, which is the second parameter to .then().).

So with that in mind, just replace your last .then() with a .done(). When you use .done(), you can just omit the rejection handler and rely on the promise library to re-throw any unhandled expections, so you will get an error description and a stack trace. With the above in mind, your code now looks like:

describe('Domain Model', function(){
  var options = { ... };

  describe('Initialize',function(){
    it('should be an string', function(done){
      //request auth key
      var promise = new Promise(function(resolve,reject){
        // ...
      });
      promise.done(function(data){
        var token = (JSON.parse(data))["access_token"];
        console.log(token);
        (token).should.be.type('string');
        done();
      });
    });
  });
});

Basically, ignoring the previous code sample, the only difference from the one before that is we're using .done() instead of .then().

Hopefully, this covers most of what you need to get started. There are other things you might want to consider, like potentially retrieving the auth key inside of a before() hook instead of an it() block (because I'm assuming the real thing you're testing is not the retrieval of the key - that's just a prerequisite to test the things you actually want to test, so a hook might be more appropriate - see here). I also question whether or not you should be connecting to an external system from within your tests at all, versus just stubbing it out (that depends on whether these are unit tests or integration tests). And I'm sure you can come up with a better assertion that just checking that token is a string, like using a regex to make sure it matches a pattern, or actually testing a request for a protected resource and making sure that it goes through. But I'll leave those questions for you to think about.

like image 145
Sergey K Avatar answered Oct 14 '22 05:10

Sergey K