Async Tests with Mocha in Node

By Amitai B.

Nov 3, 2016

Node JavaScript (JS) is asynchronous. This means it’s a pain. It’s a pain developing this way and it’s a pain writing tests for this framework.

In this blog post, I will show you how to ease this pain with the new Async/Await feature of ES7.

I love writing unit tests. It makes me develop faster and better.
When I test my code, I use it against the database. Some will argue that this is not unit testing. But I find this method to be easier with a lot of advantages, although it takes longer to run my tests.

Unit testing against the database in Ruby or Java is like testing against mocks and stubs. In Node it is different because every call to the database is asynchronous.

In this example, I’m using Mocha with Chai. The ORM I’m using is Sequelize.

The data model

My data model will be Company with employees, and every employee will have an address.

  var Company = sequelize.define('Company', {
    name: DataTypes.STRING
  }, {
    classMethods: {
      associate: function(models) {
        Company.hasMany(models.Employee);
      }
    }
  });

 var Employee = sequelize.define('Employee', {
    name: DataTypes.STRING
  }, {
    classMethods: {
      associate: function(models) {
        Employee.hasOne(models.Address);
      }
    }
  });
  return Employee;

 var Address = sequelize.define('Address', {
    city: DataTypes.STRING,
    street: DataTypes.STRING,
    house: DataTypes.INTEGER
  });

Using ES5

I would like to test that the data model is correct with all the associations. This is how it is written in ES5:

describe('company model', function () {
  beforeEach(function(done) {
    db.sequelize.sync({ force: true }) // drops table and re-creates it
      .then(function() {
        db.Company.create({ name: 'Soectory' })
        .then(function(company) {
          db.Employee.create({ name: 'Amitai Barnea' })
          .then(function(employee) {
            company.addEmployee(employee);
            db.Address.create({ city: 'Netanya' })
            .then(function (address) {
              employee.setAddress(address)
              .then(done());
            });
          });
        });
      })
      .catch(function(error) {
        done(error);
      });
  });


  it('should have all associations', function (done) {
    db.Company.findOne({id: 1 })
    .then(function (company) {
      company.getEmployees()
      .then(function (employees) {
        expect(employees.length).to.equal(1);
        expect(employees[0].get().name).to.equal('Amitai Barnea');
         employees[0].getAddress()
         .then( function (address) {
           expect(address.get().city).to.equal('Netanya');
done();
         });
      });
    });
  });
});

Oohh!! This looks awful. This is so hard to read or maintain; it is also hard to know when each function starts or ends. It is callback hell with promises!

Using ES7 Async/Await

There’s got to be a better way, right?

Yes, ES7 async/await (it is a new feature of Node 7) is here to help. This is how it looks in ES7:

describe('company model', () => {
  beforeEach((done) => {
    db.sequelize.sync({ force: true }) // drops table and re-creates it
      .then(async () => {
        const company = await db.Company.create({ name: 'Spectory' });
        const employee = await db.Employee.create({ name: 'Amitai Barnea' });
        await company.addEmployee(employee);
        const address1 = await db.Address.create({ city: 'Netanya' });
        await employee.setAddress(address1);
        done();
      })
      .catch((error) => {
        done(error);
      });
  });


  it('should have all associations', async () => {
    const company = await db.Company.findOne({id: 1 });
    const employees = await company.getEmployees();
    expect(employees.length).to.equal(1);
    expect(employees[0].get().name).to.equal('Amitai Barnea');
    const address = await employees[0].getAddress();
    expect(address.get().city).to.equal('Netanya');
  });
});

So much cleaner! I can read it and can add more scenarios. It makes sense. Here are some things to take note of:

  • async – at the main functions.
  • await for every async call.
  • I’m not using “done” in the test.

How to activate mocha with ES7?

Super easy, just add babel:

./node_modules/.bin/mocha --compilers js:babel-core/register test/server -w --recursive

Conclusion

Unit testing is fundamental in agile deployment. It is becoming more and more crucial for the deployment process because we use methods like CI/CD.

We want to make sure that the application we are deploying is not broken. We prefer using automated tests rather than any test via human effort. Although all developers know that, many of them tend to skip these tests or to use them less frequently than they should.

Today, we have great tools for unit testing, and this area is evolving fast.

So, take advantage of these tools and do some tests!

Leave a Reply

Your email address will not be published.