javascripttypescriptjestjsmockingts-jest

Trying to mock all the functions of a JavaScript class with Jest


I am trying to mock a JavaScript service class (AppInsightsService) that I pass in to a tool class (OrderHistoryTool) to use. I don't want to return any data from the mock - I just want to ignore all calls to the service functions, and also allow me to pass in the mock when I create my tool that uses that service.

I have tried various and sundry examples that I have seen when searching online, but they are not working for me. I'm sure I'm doing something wrong, but I'm not sure what it is.

Here is a nutshell of the service class that I want to mock:

@Injectable()
export class AppInsightsService {
  constructor(private readonly config: ConfigService) {
...
  }

  async postEvent(appInsightsEventDTO: AppInsightsEventDTO) {
...
  }
...

Here is the tool class that uses the service:

export class OrderHistoryTool extends DynamicStructuredTool {
  constructor(
    ...
    appInsightsService: AppInsightsService,
  ) {
    super({
...
      }),
      func: async (payload: OrderHistoryInput): Promise<string> => {
        appInsightsService.postEvent(
          {
            name: 'OrderHistoryRequested',
            properties: {
              conversationId: conversationId,
              athleteId: athleteId,
            },
          });
...

Here is my test:

const appInsightsServiceMock = jest.mock('../../app-insights/app-insights.service', jest.fn());
import { AppInsightsService } from '../../app-insights/app-insights.service';

enableFetchMocks();

// NOTE: TRIED USING THIS TOO BUT SAME ERROR
// jest.mock('../../app-insights/app-insights.service', () => {
//   return {
//     AppInsightsService: jest.fn().mockImplementation(() => {
//       return jest.fn().mockImplementation(() => {
//         return undefined;
//       });
//     }),
//   };
// });

describe('orderHistory - unit tests', () => {

  it('verify correct output', async () => {

    // NOTE: TRIED USING THIS TOO BUT SAME ERROR
    // const appInsightsServiceMock = jest.fn();

    const orderHistoryTool = new OrderHistoryTool(
      ...
      appInsightsServiceMock as unknown as AppInsightsService,
    );

    const result = await orderHistoryTool.invoke(
      {
        orderNumber: '123456',
      },
    );

I am getting an error when I try to run the test. It is failing when it tries to execute the postEvent() function from the service:

    [tool/error] [1:tool:order_history] [37ms] Tool run errored with error: "appInsightsService.postEvent is not a function
    TypeError: appInsightsService.postEvent is not a function\n    at OrderHistoryTool.func (orderHistory.ts:63:28)\n    at OrderHistoryTool._call 

How do I create a mock of the class that will mock all the functions, and allow the user to call those functions as if they are real?


Solution

  • You should mock the appInsightsService to satisfy the AppInsightsService interface. Since it will call appInsightsService.postEvent() method when calling the orderHistoryTool.invoke() method, you should define this method for appInsightsServiceMock mock object.

    import { AppInsightsService } from './app-insights.service';
    import { OrderHistoryTool } from './order-history-tool.service';
    
    describe('orderHistory - unit tests', () => {
      it('verify correct output', async () => {
        const appInsightsServiceMock = {
          postEvent: jest.fn(),
        };
        const orderHistoryTool = new OrderHistoryTool(appInsightsServiceMock as unknown as AppInsightsService);
        await orderHistoryTool.invoke({ orderNumber: '123456' });
        expect(appInsightsServiceMock.postEvent).toHaveBeenCalledTimes(1);
      });
    });
    

    Test result:

     PASS  stackoverflow/78845864/order-history-tool.service.test.ts (6.518 s)
      orderHistory - unit tests
        √ verify correct output (1 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        16.388 s
    Ran all test suites related to changed files.