簡體   English   中英

將 TypeORM 存儲庫注入 NestJS 服務以進行模擬數據測試

[英]Inject TypeORM repository into NestJS service for mock data testing

在這個問題上有一個關於如何做到這一點的長期討論。

我已經嘗試了許多建議的解決方案,但我運氣不佳。

誰能提供一個具體的例子來說明如何使用注入的存儲庫和模擬數據來測試服務?

假設我們有一個非常簡單的服務,它通過 id 查找用戶實體:

export class UserService {
  constructor(@InjectRepository(UserEntity) private userRepository: Repository<UserEntity>) {
  }

  async findUser(userId: string): Promise<UserEntity> {
    return this.userRepository.findOne(userId);
  }
}

然后您可以使用以下模擬工廠模擬UserRepository (根據需要添加更多方法):

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

使用工廠可確保每次測試都使用新的模擬。

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);
  });
});

為了類型安全和舒適,您可以為您的(部分)模擬使用以下類型(遠非完美,當 jest 本身在即將到來的主要版本中開始使用 typescript 時可能會有更好的解決方案):

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

我的解決方案使用 sqlite 內存數據庫,我在其中插入所有需要的數據並在每次測試運行之前創建模式。 因此,每個測試都使用相同的數據集,您不必模擬任何 TypeORM 方法:

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);
  });
});

我在這里得到了啟發: https ://gist.github.com/Ciantic/be6a8b8ca27ee15e2223f642b5e01549

您還可以使用測試數據庫並在那里插入數據。

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([]);
    });
  });
});

您將需要手動創建數據庫,例如psql -U postgres -c 'create database test;' . 架構同步將自動發生。

我還發現這對我有用:

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

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

從上述想法開始並幫助模擬任何類,我們提出了這個 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();

首先,我是 Ts/Js/Node 的新手。 這是我的示例代碼:它允許您在測試期間將 NEST 的注入系統與自定義連接一起使用。 以這種方式,服務/控制器對象不是手動創建的,而是由 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);
  });
});

與上一個答案中定義的建議 MockTypes 類似的是 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; };

這是一種可以與 MockType 一樣使用的實用程序類型,但不同之處在於您的原始方法簽名的有效負載將是相同的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM