0

I want to write a unit test for a controller in NestJS which uses the service. Service uses an entity and typeorm to getting data from postgres.

controller.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { EmissionsWeldingController } from '../EmissionsWelding.controller';

describe('EmissionsWeldingController', () => {
  let controller: EmissionsWeldingController;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [EmissionsWeldingController],
    }).compile();

    controller = module.get<EmissionsWeldingController>(EmissionsWeldingController);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
});

It failed with an error: Nest can't resolve dependencies of the EmissionsWeldingController (?). Please make sure that the argument EmissionsWeldingService at index [0] is available in the RootTestModule context.

When I define my service and entity

import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { EmissionsWeldingController } from '../EmissionsWelding.controller';
import { Mark, Substance, WorkType, WorkSpecification, InputField, SummaryValue } from '../EmissionsWelding.entity';
import { EmissionsWeldingService } from '../EmissionsWelding.service';

describe('EmissionsWeldingController', () => {
  let controller: EmissionsWeldingController;
  let service: EmissionsWeldingService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [TypeOrmModule.forFeature([Mark, Substance, WorkType, WorkSpecification, InputField, SummaryValue])],
      controllers: [EmissionsWeldingController],
      providers: [EmissionsWeldingService],
    }).compile();

    controller = module.get<EmissionsWeldingController>(EmissionsWeldingController);
    service = module.get<EmissionsWeldingService>(EmissionsWeldingService);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
});

It's failing with an error: Nest can't resolve dependencies of the MarkRepository (?). Please make sure that the argument Connection at index [0] is available in the TypeOrmModule context.

How should I define providers and entities without getting above error?

service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { GetResultDto } from './dto/GetResult.dto';
import { InputField, Mark, Substance, SummaryValue, WorkSpecification, WorkType } from './EmissionsWelding.entity';

@Injectable()
export class EmissionsWeldingService {
  constructor(
    @InjectRepository(Mark)
    private markRepository: Repository<Mark>,
    @InjectRepository(Substance)
    private substanceRepository: Repository<Substance>,
    @InjectRepository(WorkSpecification)
    private workSpecificationRepository: Repository<WorkSpecification>,
    @InjectRepository(WorkType)
    private workTypeRepository: Repository<WorkType>,
    @InjectRepository(InputField)
    private inputFieldRepository: Repository<InputField>,
    @InjectRepository(SummaryValue)
    private summaryValueRepository: Repository<SummaryValue>,
  ) {}

  async getMarks(work_type_id: number, work_specification_id: number): Promise<Mark[]> {
    return await this.markRepository.find({ where: { work_type_id, work_specification_id } });
  }

  async getSubstances(): Promise<Substance[]> {
    return await this.substanceRepository.find();
  }

  async getWorkSpecifications(): Promise<WorkSpecification[]> {
    return await this.workSpecificationRepository.find();
  }

  async getWorkTypes(): Promise<WorkType[]> {
    return await this.workTypeRepository.find();
  }

  async getInputFields(): Promise<WorkType[]> {
    return await this.inputFieldRepository.find();
  }

  async getSummaryValues(mark_id: number, substance_id: number): Promise<SummaryValue[]> {
    return await this.summaryValueRepository.find({ where: { mark_id, substance_id } });
  }

  async getResult(body: GetResultDto): Promise<GetResultDto[]> {
    const result = [];
    const { mark_id, input_fields_values } = body;
    const substances = await this.getSubstances();
    let currentSummaryValue;

    for (let i = 0; i <= substances.length - 1; i++) {
      currentSummaryValue = await this.getSummaryValues(mark_id, i + 1);
      result.push({
        code: substances[i].code,
        name: substances[i].name,
        year:
          ((input_fields_values.year * currentSummaryValue[0].value) / 10 ** 6) *
          (1 - (input_fields_values.clean ? input_fields_values.clean : 0)),
        second:
          ((currentSummaryValue[0].value * input_fields_values.hour) / 3600) *
          (1 - (input_fields_values.clean ? input_fields_values.clean : 0)),
      });
    }
    return result;
  }
}

controller.ts

import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { GetResultDto } from './dto/GetResult.dto';
import { EmissionsWeldingService } from './EmissionsWelding.service';

@ApiTags('EmissionsWelding')
@Controller('EmissionsWelding')
export class EmissionsWeldingController {
  constructor(private emissionsWeldingService: EmissionsWeldingService) {}

  @Get('getMarks')
  getMarks(@Query('work_type_id') work_type_id: number, @Query('work_specification_id') work_specification_id: number) {
    return this.emissionsWeldingService.getMarks(work_type_id, work_specification_id);
  }

  @Get('getSubstances')
  getSubstances() {
    return this.emissionsWeldingService.getSubstances();
  }

  @Get('getWorkSpecifications')
  getWorkSpecifications() {
    return this.emissionsWeldingService.getWorkSpecifications();
  }

  @Get('getWorkTypes')
  getWorkTypes() {
    return this.emissionsWeldingService.getWorkTypes();
  }

  @Get('getInputFields')
  getInputFields() {
    return this.emissionsWeldingService.getInputFields();
  }

  @Post('getResult')
  getResult(@Body() body: GetResultDto) {
    return this.emissionsWeldingService.getResult(body);
  }
}
Antonio
  • 299
  • 1
  • 4
  • 13
  • can you show the code for `EmissionsWeldingController`? There is something in the constructor that you need to inject in the createTestingModule function. It looks like you have a mistake in your second attempt: `let service: EmissionsWeldingController;`, this should be EmissionsWeldingService right? – Eamonn McEvoy May 05 '22 at 13:50
  • I would also recommend having separate unit tests for your controller and service to separate the concerns of handling http input (controller) and your business logic (service) – Eamonn McEvoy May 05 '22 at 13:56
  • I replace `let service: EmissionsWeldingController` on `let service: EmissionsWeldingService`. check it. The error is still there. I import the service in the controller's test file from the first error – Antonio May 05 '22 at 14:03
  • Could you post the code from `EmissionsWeldingController` and `EmissionsWeldingService`. Check the constructor parameters from both classes, you need to configure testing module to be able to inject every dependency. In your first example `EmissionsWeldingController (?)` I assume `EmissionsWeldingService` is the first parameter? Then in the second example `MarkRepository (?)` it cannot construct the `MarkRepository` class because there is a missing dependency. I think this answer will show you how to satisfy the MarkRepository dependency https://stackoverflow.com/a/55366343/588734 – Eamonn McEvoy May 05 '22 at 15:29
  • I added Service and Controller, check it – Antonio May 05 '22 at 15:47
  • You have a bit of a pickle on your hands here with the number of mocks you need to create. You might want to consider breaking the service up in to multiple classes and try to adhere to SRP, this will make your tests more manageable. good luck! – Eamonn McEvoy May 08 '22 at 15:05

1 Answers1

-1

You need to provide a mock for every repository that is injected into your service.

This answer provides the necessary detail: https://stackoverflow.com/a/55366343/588734

describe('EmissionsWeldingController', () => {
  let controller: EmissionsWeldingController;
  let service: EmissionsWeldingService;
  let markRepositoryMock: MockType<Repository<Mark>>;
  // repeat for every repository let __RepositoryMock: MockType<Repository<__>>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [EmissionsWeldingController],
      providers: [
        EmissionsWeldingService,
        { 
          provide: getRepositoryToken(Mark),
          useValue: jest.fn(() => ({
            findOne: jest.fn(entity => entity),
            // provide a mock for each function you are calling
          })) 
        },
        // repeat for every mock
      ],
    }).compile();

    controller = module.get<EmissionsWeldingController>(EmissionsWeldingController);
    service = module.get<EmissionsWeldingService>(EmissionsWeldingService);
    markRepositoryMock = module.get(getRepositoryToken(Mark));
    // repeat for every mock repository

  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
});
halfer
  • 19,824
  • 17
  • 99
  • 186
Eamonn McEvoy
  • 8,876
  • 14
  • 53
  • 83