How to mock typeORM's getCustomRepository

Viewed 11253

I want to unit-test a class which getCustomRepository in it's constructor but I just can't figure an easy way to mock it. Here is my class code

import {getCustomRepository} from 'typeorm';

export class Controller {
  private repository: UserRepository;

  constructor() {
    this.repository = getCustomRepository(UserRepository); //I want to mock this.
  }

  async init() {
    return this.repository.findUser(1);
  }
}

and here are tests

describe('User Tests', () => {
  it('should return user', async () => {
    //Fake user to be resolved.
    const user = new User();
    user.id = 2;

    //I want to mock  getCustomRepository(UserRepository); here
    //getCustomRepository = jest.fn().mockResolvedValue(UserRepository); HERE HOW???

    //Mocking find user
    UserRepository.prototype.findUser = jest.fn().mockResolvedValue(user);

    const controller = new Controller();
    const result = await controller.init();
    expect(result).toBeDefined();
  });
});

Note: Mocking repository methods works well but I really want to mock getCustomRepository so as It may reduce time which is wasted trying to connect to database.

This is how getCustomRepository in typeORM looks like

export declare function getCustomRepository<T>(customRepository: ObjectType<T>, connectionName?: string): T;

UserRepository.ts

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  public async findUser(id: number) {
    return 'real user';
  }
}

User.ts

@Entity('users')
export class User{
  @PrimaryGeneratedColumn()
  id: number;

  @Column({type: 'varchar', length: 100})
  name: string;
}

So question is how do I mock it? Any help will be much appreciated.

3 Answers

You can use jest.mock(moduleName, factory, options) to mock typeorm module, getCustomRepository function and its returned value.

E.g.

controller.ts:

import { getCustomRepository } from 'typeorm';
import { UserRepository } from './userRepo';

export class Controller {
  private repository: UserRepository;

  constructor() {
    this.repository = getCustomRepository(UserRepository);
  }

  async init() {
    return this.repository.findUser(1);
  }
}

userRepo.ts:

export class UserRepository {
  public async findUser(id: number) {
    return 'real user';
  }
}

controller.test.ts:

import { Controller } from './controller';
import { getCustomRepository } from 'typeorm';
import { mocked } from 'ts-jest/utils';
import { UserRepository } from './userRepo';

jest.mock('typeorm', () => ({ getCustomRepository: jest.fn() }));

describe('61693597', () => {
  it('should pass', async () => {
    const userRepo = { findUser: jest.fn().mockResolvedValueOnce('fake user') };
    mocked(getCustomRepository).mockReturnValueOnce(userRepo);
    const controller = new Controller();
    const actual = await controller.init();
    expect(actual).toBe('fake user');
    expect(getCustomRepository).toBeCalledWith(UserRepository);
    expect(userRepo.findUser).toBeCalledWith(1);
  });
});

unit test results with coverage report:

 PASS  stackoverflow/61693597/controller.test.ts (13.53s)
  61693597
    ✓ should pass (9ms)

---------------|---------|----------|---------|---------|-------------------
File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------|---------|----------|---------|---------|-------------------
All files      |   92.31 |      100 |      80 |   90.91 |                   
 controller.ts |     100 |      100 |     100 |     100 |                   
 userRepo.ts   |      80 |      100 |      50 |      75 | 3                 
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        15.596s

source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61693597

As previously mentioned in a comment, I have tried replicating @slideshowp2's answer. The mocked() util from ts-jest was returning 'mockResolvedValueOnce' as undefined.

To solve it, I had to break it down into some steps:

  1. include sinon as a dependency to perform more in-depth mocking
  2. (very important) create a wrapper for the getCustomRepository function

Without the wrapper, it is virtually impossible to mock the return since the property is readonly. It should be enough to do something like:

c: Connection;

public async getCustomRepository<T>(type: ObjectType<T>): Promise<T> {
    if (!this.c || !this.c.isConnected) {
      this.c = await this.getConnection();
    }
    return await this.c.getCustomRepository(type);
  }

After doing so, I have managed to mock the getCustomRepository function like so:

describe('Unit tests', () => {
let sandbox: sinon.SinonSandbox;
let useCase: FindNodeUseCase;
let repositoryStub: sinon.SinonStub;
let nodeTreeRepo: NodeTreeRepository;

beforeEach(() => {
  sandbox = sinon.createSandbox();
  useCase = new FindNodeUseCase();

  const mockNode = new NodeTree();
  mockNode.idNode = 1;
  mockNode.level = 1;
  nodeTreeRepo = new NodeTreeRepository()
  nodeTreeRepo.findOne = jest.fn().mockResolvedValue(mockNode)
  nodeTreeRepo.findByIdAndLanguage = jest.fn().mockReturnValue(
    [
      [{ idNode: 1, level: 2, names: [{ idNode: 1, nodeName:"Sales" }] }],
      1
    ]
  );
  repositoryStub = sandbox.stub(useCase.databaseManager, 'getCustomRepository').resolves(nodeTreeRepo)
})

afterEach(() => {
  sandbox.restore();
})

I've run into the same issue a bit later as it seems. Here is for future visitors a version that is currently working:

my spec.ts file

import { Test, TestingModule } from '@nestjs/testing';

import { GraphDataService } from './graph-data.service';

jest.mock('typeorm', () => ({
  ...jest.requireActual('typeorm'),
  getCustomRepository: jest.fn(),
}));

describe('GraphDataService', () => {
  let service: GraphDataService;

  beforeAll(() =>
    jest.mock('typeorm', () => ({
      ...jest.requireActual('typeorm'),
      getCustomRepository: jest.fn(),
    })),
  );

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [GraphDataService, { provide: 'ObjectValidator', useValue: {} }],
    }).compile();

    service = module.get<GraphDataService>(GraphDataService);
  });

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

Works with nestjs@7.6.1 and typeorm@0.2.26

Related