Mocha.js: async function breaks the nested structure

703 Views Asked by At

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("[email protected]"); });
        it ("phone", () => { UserData.phone.should.equal("+1 (800) 123 4567"); });
    });
});

function getUserData(param) { return new Promise(resolve => setTimeout(() => resolve({ email:"[email protected]",phone:"+1 (800) 123 4567" }), 1/*ms*/));}
3

There are 3 best solutions below

0
richytong On BEST ANSWER

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("[email protected]"); });
        it ("phone", () => { UserData.phone.should.equal("+1 (800) 123 4567"); });
    });
});

function getUserData(param) { return new Promise(resolve => setTimeout(() => resolve({ email:"[email protected]",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

0
Pedro Fracassi On

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("[email protected]");
  done();
});
2
hoangdv On

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("[email protected]"); });
        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("[email protected]"); });
    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("[email protected]");
      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)