2

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*/));}
Dani-Br
  • 2,289
  • 5
  • 25
  • 32

3 Answers3

1

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)
hoangdv
  • 15,138
  • 4
  • 27
  • 48
  • Thank you. But it should be `before`, instead of `beforeEach`. My real function takes long time to run, and i'm checking much more than 2 properties. – Dani-Br May 22 '20 at 15:59
  • 1
    @Dani-Br The task of the unit test takes long time to run? Some thinh went wrong. BTW, you can use `beforeAll` instead of `beforeEach`, `beforeAll` just executes once. – hoangdv May 23 '20 at 03:29
0

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();
});
Pedro Fracassi
  • 3,342
  • 2
  • 16
  • 33
0

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

richytong
  • 2,387
  • 1
  • 10
  • 21