Inject TypeORM repository into NestJS service for mock data testing

前端 未结 3 1747
借酒劲吻你
借酒劲吻你 2020-11-27 18:12

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.

C

相关标签:
3条回答
  • 2020-11-27 18:31

    Let's assume we have a very simple service that finds a user entity by id:

    export class UserService {
      constructor(@InjectRepository(UserEntity) private userRepository: Repository<UserEntity>) {
      }
    
      async findUser(userId: string): Promise<UserEntity> {
        return this.userRepository.findOne(userId);
      }
    }
    

    Then you can mock the UserRepository with the following mock factory (add more methods as needed):

    // @ts-ignore
    export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
      findOne: jest.fn(entity => entity),
      // ...
    }));
    

    Using a factory ensures that a new mock is used for every test.

    describe('UserService', () => {
      let service: UserService;
      let repositoryMock: MockType<Repository<UserEntity>>;
    
      beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
          providers: [
            UserService,
            // Provide your mock instead of the actual repository
            { provide: getRepositoryToken(UserEntity), useFactory: repositoryMockFactory },
          ],
        }).compile();
        service = module.get<UserService>(UserService);
        repositoryMock = module.get(getRepositoryToken(UserEntity));
      });
    
      it('should find a user', async () => {
        const user = {name: 'Alni', id: '123'};
        // Now you can control the return value of your mock's methods
        repositoryMock.findOne.mockReturnValue(user);
        expect(service.findUser(user.id)).toEqual(user);
        // And make assertions on how often and with what params your mock's methods are called
        expect(repositoryMock.findOne).toHaveBeenCalledWith(user.id);
      });
    });
    

    For type safety and comfort you can use the following typing for your mocks (far from perfect, there might be a better solution when jest itself starts using typescript in the upcoming major releases):

    export type MockType<T> = {
      [P in keyof T]: jest.Mock<{}>;
    };
    
    0 讨论(0)
  • 2020-11-27 18:32

    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

    0 讨论(0)
  • 2020-11-27 18:52

    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();
    
    0 讨论(0)
提交回复
热议问题