Mocha.js: async function breaks the nested structure

Viewed 447

When testing a result of async function, using mocha, the tests that comes after await pops out of the nested structure, like the first 2 tests below:

✓ email
✓ phone
current unit
    fetch data
    ✓ is 5==5

3 passing (10ms)

How can we make the tests to appear in their proper place?

The code:

const chai = require('chai');
chai.should();

describe ("current unit", async () => {    
    describe ("fetch data", async () => {    
        it ("is 5==5", () => { chai.expect(5).to.equal(5); });

        const UserData = await getUserData("UserName");

        it ("email", () => { UserData.email.should.equal("example@g.com"); });
        it ("phone", () => { UserData.phone.should.equal("+1 (800) 123 4567"); });
    });
});

function getUserData(param) { return new Promise(resolve => setTimeout(() => resolve({ email:"example@g.com",phone:"+1 (800) 123 4567" }), 1/*ms*/));}
3 Answers

If you "convert" your code from async/await syntax to Promise syntax, it will look more clear to explain:

describe("current unit", () => {
  describe("fetch data", () => {
    it("is 5==5", () => { chai.expect(5).to.equal(5); });

    getUserData("UserName")
      .then(UserData => {
        it("email", () => { UserData.email.should.equal("example@g.com"); });
        it("phone", () => { UserData.phone.should.equal("+1 (800) 123 4567"); });
      });
  });
});

As you can see, "fetch data" just includes is 5==5, and email, phone specs are in another scope (in this case the scope is free describe) then these specs will be appear on the top.

getUserData just "waits" 1ms, then you can see email, phone spec, if you increase the value to 100ms (or higher) you will not these specs, because getUserData().then is a synchronous block.

Never call async action in body of describe directly, let use beforeEach, or write it in the body of it.

Use beforeEach:

describe("current unit", () => { // remove async keyword, it does not make sense
  let UserData; // define variable
  beforeEach(async () => { // async
    UserData = await getUserData("UserName"); // init
  });

  describe("fetch data", () => { // remove async keyword
    it("is 5==5", () => { chai.expect(5).to.equal(5); });
    it("email", () => { UserData.email.should.equal("example@g.com"); });
    it("phone", () => { UserData.phone.should.equal("+1 (800) 123 4567"); });
  });
});
  current unit
    fetch data
      ✓ is 5==5
      ✓ email
      ✓ phone
  3 passing (357ms)

Write in it block:

describe("current unit", () => { // remove async keyword, it does not make sense
  describe("fetch data", () => { // remove async keyword
    it("is 5==5", () => { chai.expect(5).to.equal(5); });
    it("should return correct email and phone", async () => { // compile into 1 spec
      const UserData = await getUserData("UserName");
      UserData.email.should.equal("example@g.com");
      UserData.phone.should.equal("+1 (800) 123 4567");
    });
  });
});
  current unit
    fetch data
      ✓ is 5==5
      ✓ should return correct email and phone (108ms)
  2 passing (149ms)

You need to call the done() function after the async parts of your test. Here's an example:

it ("email", (done) => {
  UserData.email.should.equal("example@g.com");
  done();
});

Here is your test file using before

const chai = require('chai');
chai.should();

describe ("current unit", async () => {    
    describe ("fetch data", async () => {    
        let UserData
        before(async () => {
            UserData = await getUserData("UserName");
        })
        it ("is 5==5", () => { chai.expect(5).to.equal(5); });
        it ("email", () => { UserData.email.should.equal("example@g.com"); });
        it ("phone", () => { UserData.phone.should.equal("+1 (800) 123 4567"); });
    });
});

function getUserData(param) { return new Promise(resolve => setTimeout(() => resolve({ email:"example@g.com",phone:"+1 (800) 123 4567" }), 1/*ms*/));}

Here is the output for the above test

  current unit
    fetch data
      ✓ is 5==5
      ✓ email
      ✓ phone

You can make the tests appear in their proper place by using a before expression in the "fetch data" test suite

Related