'Inject TypeORM repository into NestJS service for mock data testing

There's a longish discussion about how to do this in this issue.

I've experimented with a number of the proposed solutions but I'm not having much luck.

Could anyone provide a concrete example of how to test a service with an injected repository and mock data?



Solution 1:[1]

My solution uses sqlite memory database where I insert all the needed data and create schema before every test run. So each test counts with the same set of data and you do not have to mock any TypeORM methods:

import { Test, TestingModule } from "@nestjs/testing";
import { CompanyInfo } from '../../src/company-info/company-info.entity';
import { CompanyInfoService } from "../../src/company-info/company-info.service";
import { Repository, createConnection, getConnection, getRepository } from "typeorm";
import { getRepositoryToken } from "@nestjs/typeorm";

describe('CompanyInfoService', () => {
  let service: CompanyInfoService;
  let repository: Repository<CompanyInfo>;
  let testingModule: TestingModule;

  const testConnectionName = 'testConnection';

  beforeEach(async () => {
    testingModule = await Test.createTestingModule({
      providers: [
        CompanyInfoService,
        {
          provide: getRepositoryToken(CompanyInfo),
          useClass: Repository,
        },
      ],
    }).compile();

    let connection = await createConnection({
        type: "sqlite",
        database: ":memory:",
        dropSchema: true,
        entities: [CompanyInfo],
        synchronize: true,
        logging: false,
        name: testConnectionName
    });    

    repository = getRepository(CompanyInfo, testConnectionName);
    service = new CompanyInfoService(repository);

    return connection;
  });

  afterEach(async () => {
    await getConnection(testConnectionName).close()
  });  

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

  it('should return company info for findOne', async () => {
    // prepare data, insert them to be tested
    const companyInfoData: CompanyInfo = {
      id: 1,
    };

    await repository.insert(companyInfoData);

    // test data retrieval itself
    expect(await service.findOne()).toEqual(companyInfoData);
  });
});

I got inspired here: https://gist.github.com/Ciantic/be6a8b8ca27ee15e2223f642b5e01549

Solution 2:[2]

You can also use a test DB and insert data there.

describe('EmployeesService', () => {
  let employeesService: EmployeesService;
  let moduleRef: TestingModule;

  beforeEach(async () => {
    moduleRef = await Test.createTestingModule({
      imports: [
        TypeOrmModule.forFeature([Employee]),
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: 'db',
          port: 5432,
          username: 'postgres',
          password: '',
          database: 'test',
          autoLoadEntities: true,
          synchronize: true,
        }),
      ],
      providers: [EmployeesService],
    }).compile();

    employeesService = moduleRef.get<EmployeesService>(EmployeesService);
  });

  afterEach(async () => {
    // Free DB connection for next test
    await moduleRef.close();
  });

  describe('findOne', () => {
    it('returns empty array', async () => {
      expect(await employeesService.findAll()).toStrictEqual([]);
    });
  });
});

You will need to create the DB manually, e.g. psql -U postgres -c 'create database test;'. Schema sync will happen automatically.

Solution 3:[3]

I also found that this worked for me:

export const mockRepository = jest.fn(() => ({
  metadata: {
    columns: [],
    relations: [],
  },
}));

and

const module: TestingModule = await Test.createTestingModule({
      providers: [{ provide: getRepositoryToken(Entity), useClass: mockRepository }],
    }).compile();

Solution 4:[4]

Starting with the above ideas and to help with mocking any class, we came out with this MockFactory:

export type MockType<T> = {
    [P in keyof T]?: jest.Mock<unknown>;
};

export class MockFactory {
    static getMock<T>(type: new (...args: any[]) => T, includes?: string[]): MockType<T> {
        const mock: MockType<T> = {};

        Object.getOwnPropertyNames(type.prototype)
            .filter((key: string) => key !== 'constructor' && (!includes || includes.includes(key)))
            .map((key: string) => {
                mock[key] = jest.fn();
            });

        return mock;
    }
}

const module: TestingModule = await Test.createTestingModule({
    providers: [
        {
            provide: getRepositoryToken(MyCustomRepository),
            useValue: MockFactory.getMock(MyCustomRepository)
        }
    ]
}).compile();

Solution 5:[5]

First of all I'm new to Ts/Js/Node. Here is my example code : it lets you use NEST's injection system with a custom Connection during tests. In this manner service/controller objects are not created by hand but wired by the TestingModule:

import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import {
  Repository,
  createConnection,
  getConnection,
  getRepository,
  Connection,
} from 'typeorm';
import { Order } from './order';
import { OrdersService } from './orders.service';

describe('Test Orders', () => {
  let repository: Repository<Order>;
  let service: OrdersService;
  let connection: Connection;
  beforeEach(async () => {
    connection = await createConnection({
      type: 'sqlite',
      database: './test.db',
      dropSchema: true,
      entities: [Order],
      synchronize: true,
      logging: true,
    });
    repository = getRepository(Order);
    const testingModule = await Test.createTestingModule({
      providers: [
        OrdersService,
        {
          provide: getRepositoryToken(Order, connection),
          useFactory: () => {
            return repository;
          },
        },
      ],
    }).compile();
    console.log('Getting Service from NEST');
    service = testingModule.get<OrdersService>(OrdersService);
    return connection;
  });

  afterEach(async () => {
    await getConnection().close();
  });

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

  it('CRUD Order Test', async () => {
    const order = new Order();
    order.currency = 'EURO';
    order.unitPrice = 12.0;
    order.issueDate = new Date();
    const inserted = await service.create(order);
    console.log('Inserted order ', inserted.id); // id is the @PrimaryGeneratedColumn() key
    let allOrders = await service.findAll();
    expect(allOrders.length).toBe(1);
    await service.delete(inserted.id);
    allOrders = await service.findAll();
    expect(allOrders.length).toBe(0);
  });
});

Solution 6:[6]

Something similar to the suggested MockTypes defined in the previous answer is the TypedMockType

type ArgsType<T> = T extends (...args: infer A) => unknown ? A : never;

export type TypedMockType<T> = {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [P in keyof T]: T[P] extends (...args: any) => unknown
    ? jest.Mock<ReturnType<T[P]>, ArgsType<T[P]>>
    : never;
};

This is a utility type that can be used the same as MockType, but the difference is that your payloads of the original method signature will be the same.

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 michal.jakubeczy
Solution 2
Solution 3 Vincil Bishop
Solution 4 SPAS
Solution 5 Mike
Solution 6 MathGainz