Treecj
Treecj

Reputation: 427

Jest: Test recursive call inside Promise

I'm having some problems testing the following class.

interface Connector {
   connect: () => Promise<void>;
}

class Unit {
   private connector: Connector;

   constructor(connector: Connector) {
      this.connector = connector;
   }

   public attemptConnect(iteration: number, max: number): void {
      console.log("attempt" + iteration);

      this.connector.connect()
         .then(() => {
            console.log("connected");
         })
         .catch((error) => {
            if (iteration < max) {
               this.attemptConnect(iteration + 1, max);
            }
         });
   }
}

I would like to test that the Connector.connect() function is called the right amount of times. I'm trying to achieve that with jest as follows:

describe("Unit", () => {
   it("fails to record more than one recursion", async () => {
      const connector: Connector = {
         connect: jest.fn().mockRejectedValue(new Error("foo")),
      };
      const unit: Unit = new Unit(connector);

      await unit.attemptConnect(1, 4);

      expect((connector.connect as jest.Mock).mock.calls.length).toBe(4);
   });
});

Unfortunately the expect() call fails, saying

Error: expect(received).toBe(expected) // Object.is equality

Expected: 4
Received: 1

If I use a debugger and watch the value of

(connector.connect as jest.Mock).mock.calls.length

I can see the calls being correctly recorded. Only when the test finishes the number is wrong.

Thank you for any help!

Upvotes: 2

Views: 1270

Answers (1)

Brian Adams
Brian Adams

Reputation: 45830

Solution 1

Return the promise in attemptConnect():

public attemptConnect(iteration: number, max: number): Promise<void> {
  console.log("attempt" + iteration);

  return this.connector.connect()
    .then(() => {
      console.log("connected");
    })
    .catch((error) => {
      if (iteration < max) {
        return this.attemptConnect(iteration + 1, max);
      }
    });
}

Solution 2

await for the required number of event loop cycles in the test:

describe("Unit", () => {
  it("fails to record more than one recursion", async () => {
    const connector: Connector = {
      connect: jest.fn().mockRejectedValue(new Error("foo")),
    };
    const unit: Unit = new Unit(connector);

    unit.attemptConnect(1, 4);

    // await enough event loop cycles for all the callbacks queued
    // by then() and catch() to run, in this case 5:
    await Promise.resolve().then().then().then().then();

    expect((connector.connect as jest.Mock).mock.calls.length).toBe(4);
  });
});

Details

The test awaits attemptConnect() but since it is not returning anything there is nothing to await and the synchronous test continues executing. The expect() runs and fails before the callbacks queued by then() and catch() have a chance to run.

Upvotes: 2

Related Questions