Jest mock multiple methods of a class using module factory parameter

Viewed 842

I have this class:

class DataService {
    constructor() {
    }

    async CreateItem() {
        try {
            return {data: true, error: null}
        } catch (error) {
            return {data: null, error: error}
        }
    }

    async SendToAnalytics() {
        try {
            return {data: true, error: null}
        } catch (error) {
            return {data: null, error: error}
        }
    }
    async SendEmail() {
        try {
            return {data: true, error: null}
        } catch (error) {
            return {data: null, error: error}
        }
    }

   
}

module.exports = DataService;

And I am calling these methods in my Lambda handler:

const DataService = require("../services/data.service");

exports.handler = async (event) => {
  try {
    const dataService = new DataService();
    const createItem = await dataService.CreateItem();
    if (createItem.error) {
      return {statusCode: 500};
    }
    const sendToAnalytics = await dataService.SendToAnalytics();
    if (sendToAnalytics.error) {
      return {statusCode: 500};
    }
    const sendEmail = await dataService.SendEmail();
    if (sendEmail.error) {
      return {statusCode: 500};
    }
    return {statusCode: 200};
  } catch (e) {
    return {statusCode: 500};
  }
};

I would like to cover 100 percent of the code and this is my handler.spec file

const { handler } = require("./handler");
const DataService = require("../services/data.service");

jest.mock("../services/data.service", () => {
  return jest.fn().mockImplementation(() => {
    return {
      CreateItem: jest.fn(() => ({ data: true, error: null })),
      SendToAnalytics: jest.fn(() => ({ data: true, error: null })),
      SendEmail: jest.fn(() => ({ data: true, error: null })),
    };
  });
});

const dataService = new DataService();

describe("Job Apply Handler", () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  it("Successful response", async () => {
    const res = await handler();
    expect(res.statusCode).toEqual(200);
  });
  it("Error Response because of CreateItem Error", async () => {
    dataService.SaveApplicant.mockImplementationOnce(
      jest.fn(() => ({ data: null, error: true }))
    );
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
  it("Error Response because of SendToAnalytics Error", async () => {
    dataService.SendToAnalytics.mockImplementationOnce(
      jest.fn(() => ({ data: null, error: true }))
    );
    //DOES NOT ENTER inside if(sendToAnalyticsError)
    // CreateItem is still mocked to return error: true
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
  it("Error Response because of SendEmail Error", async () => {
    dataService.SendToAnalytics.mockImplementationOnce(
      jest.fn(() => ({ data: null, error: true }))
    );
    //DOES NOT ENTER inside if(sendEmail.error)
    // CreateItem is still mocked to return error: true
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
  it("Error Response because of Exception", async () => {
    dataService.SendToAnalytics.mockImplementationOnce(
      jest.fn(() => {
        throw new Error("error");
      })
    );
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
});

However, all mockImplementationOnce() are applied on the first method of the class. I guess I am mocking the methods wrong. Since I cant reset CreateItem() after is mocked to return error and all other cases end up in the first IF statement. This is Jest documentation reference

Any suggestions ?

1 Answers

Since you use async/await keyword, CreateItem, SendToAnalytics, and SendEmail of DataService are all asynchronous methods.

You should use mockFn.mockResolvedValueOnce(value) and mockFn.mockRejectedValueOnce(value) to mock resolved/rejected value for these methods.

E.g.

data.serivce.js:

class DataService {
  constructor() {}

  async CreateItem() {
    try {
      return { data: true, error: null };
    } catch (error) {
      return { data: null, error: error };
    }
  }

  async SendToAnalytics() {
    try {
      return { data: true, error: null };
    } catch (error) {
      return { data: null, error: error };
    }
  }
  async SendEmail() {
    try {
      return { data: true, error: null };
    } catch (error) {
      return { data: null, error: error };
    }
  }
}

module.exports = DataService;

handler.js:

const DataService = require('./data.service');

exports.handler = async (event) => {
  try {
    const dataService = new DataService();
    const createItem = await dataService.CreateItem();
    if (createItem.error) {
      return { statusCode: 500 };
    }
    const sendToAnalytics = await dataService.SendToAnalytics();
    if (sendToAnalytics.error) {
      return { statusCode: 500 };
    }
    const sendEmail = await dataService.SendEmail();
    if (sendEmail.error) {
      return { statusCode: 500 };
    }
    return { statusCode: 200 };
  } catch (e) {
    return { statusCode: 500 };
  }
};

handler.test.js:

const { handler } = require('./handler');

const dataService = {
  CreateItem: jest.fn(() => ({ data: true, error: null })),
  SendToAnalytics: jest.fn(() => ({ data: true, error: null })),
  SendEmail: jest.fn(() => ({ data: true, error: null })),
};
jest.mock('./data.service', () => jest.fn(() => dataService));

describe('Job Apply Handler', () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  it('Successful response', async () => {
    const res = await handler();
    expect(res.statusCode).toEqual(200);
  });
  it('Error Response because of CreateItem Error', async () => {
    dataService.CreateItem.mockResolvedValueOnce({ data: null, error: true });
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
  it('Error Response because of SendToAnalytics Error', async () => {
    dataService.SendToAnalytics.mockResolvedValueOnce({ data: null, error: true });
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
  it('Error Response because of SendEmail Error', async () => {
    dataService.SendToAnalytics.mockResolvedValueOnce({ data: null, error: true });
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
  it('Error Response because of Exception', async () => {
    dataService.SendToAnalytics.mockRejectedValueOnce(new Error('error'));
    const res = await handler();
    expect(res.statusCode).toEqual(500);
  });
});

test result:

 PASS  examples/69666288/handler.test.js (9.639 s)
  Job Apply Handler
    ✓ Successful response (4 ms)
    ✓ Error Response because of CreateItem Error
    ✓ Error Response because of SendToAnalytics Error
    ✓ Error Response because of SendEmail Error (1 ms)
    ✓ Error Response because of Exception

------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |   93.75 |    83.33 |     100 |   93.33 |                   
 handler.js |   93.75 |    83.33 |     100 |   93.33 | 16                
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        10.795 s
Related