'Manual mock with Jest in Nestjs not working

Having this mock service:

// /catalogue/__mock__/catalogue.service.ts
export const CatalogueService = jest.fn().mockImplementation(() => {
  return {
    filterRulesFor: jest.fn().mockImplementation((role: Roles): Rule[] => rules.filter(r => r.roles.includes(role))
    fetchCountries: jest.fn().mockReturnValue(of(countriesStub())),
  }
});

In the next scaffolding:

/catalogue
  /__mock__
    catalogue.service.ts
  /tests
    catalogue.controller.spec.ts
  catalogue.module.ts
  catalogue.controller.ts
  catalogue.service.ts

I want to use the mock service inside the catalogue.controller.spec.ts:

// /catalogue/tests/catalogue.controller.spec.ts
import { CatalogueController } from '../catalogue.controller';
import { CatalogueService } from '../catalogue.service';

jest.mock('../catalogue.service'); // <----- manual mocking here

describe('CatalogueController', () => {
  let controller: CatalogueController;
  let service: CatalogueService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [UtilsModule],
      controllers: [CatalogueController],
      providers: [CatalogueService],
    })
    .compile();

    controller = module.get<CatalogueController>(CatalogueController);
    service = module.get<CatalogueService>(CatalogueService);
    jest.clearAllMocks();
  });
}

But when I try to use it:

describe('getCountries', () => {
  let countries: Country[];
  beforeEach(async () => {
    service.fetchCountries()
    .subscribe(countries => console.log(countries)); // <----- here

    countries = await firstValueFrom(controller.getCountries());
  });

  it('should be called', () => {
    expect(service.fetchCountries).toBeCalledTimes(1);
  });

  it('should not be empty', () => {
    expect(countries.length).toBe(countriesStub().length);
  });
});

It gives me TypeError: Cannot read property 'subscribe' of undefined as if the mock service does not have the functions defined.

error

If I write the mock right in the same file it just works but that's not what I want:

let controller: CatalogueController;
let service: CatalogueService;

// vvvvv Mock service here vvvv
const mockCatalogueService = {
  filterRulesFor: jest.fn().mockImplementation((role: Roles): Rule[] => rules.filter(r => r.roles.includes(role))),
  fetchCountries: jest.fn().mockReturnValue(of(countriesStub())),
};

beforeEach(async () => {
  const module: TestingModule = await Test.createTestingModule({
    imports: [UtilsModule],
    controllers: [CatalogueController],
    providers: [CatalogueService],
  })
  .overrideProvider(CatalogueService) // <--- Implementing here
  .useValue(mockCatalogueService)     // <--- and here
  .compile();

  controller = module.get<CatalogueController>(CatalogueController);
  service = module.get<CatalogueService>(CatalogueService);
  jest.clearAllMocks();
});


Solution 1:[1]

Manual mock in jest only mock via import statement. But Nestjs implement DJ, this mean some objects such as service, controller,... get from container that contain all instance object of application. You can try this way

import { CatalogueService } from '../catalogue.service';

jest.mock('../catalogue.service');
//...
const module: TestingModule = await Test.createTestingModule({
  imports: [UtilsModule],
  controllers: [CatalogueController],
  providers: [CatalogueService],
})
.useMocker((token) => {
  switch (token) {
    case CatalogueService:
      return CatalogueService;
    // more mock object at here
  }
})
.compile();

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Doan Thai