首页 > 解决方案 > 如何为 @Injectable() mongodb 服务编写 Nestjs 单元测试

问题描述

有人可以指导我。我正在学习 Nestjs 并做一个小项目,但我无法让单元测试适用于依赖于 database.module 的控制器和服务。如何在 product.service.ts 中模拟 database.module?任何帮助将不胜感激。

数据库.module.ts

  try {
    const client = await MongoClient.connect(process.env.MONGODB, { useNewUrlParser: true, useUnifiedTopology: true });
    return client.db('pokemonq')
  } catch (e) {
    console.log(e);
    throw e;
  }
};

@Module({
  imports: [],
  providers: [
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: setupDbConnection
    },
  ],
  exports: ['DATABASE_CONNECTION'],
})
export class DatabaseModule {}

产品.service.ts

@Injectable()
export class ProductService {
  protected readonly appConfigObj: EnvConfig;

  constructor(
    private readonly appConfigService: AppConfigService,
    @Inject('DATABASE_CONNECTION') => **How to mock this injection?**
    private db: Db,
  ) {
    this.appConfigObj = this.appConfigService.appConfigObject;
  }

async searchBy (){}
async findBy (){}

}

product.service.spec.ts

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

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [],
      providers: [
        ConfigService,
        DatabaseModule,
        AppConfigService,
        ProductService,
        {
          provide: DATABASE_CONNECTION,
          useFactory: () => {}
        }
      ],
    }).compile();

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

  afterAll(() => jest.restoreAllMocks());

}

product.controller.spec.ts

describe('ProductController', () => {
  let app: TestingModule;
  let ProductController: ProductController;
  let ProductService: ProductService;

  const response = {
    send: (body?: any) => {},
    status: (code: number) => response,
    json: (body?: any) => response
  }

  beforeEach(async () => {
    app = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({
          load: [appConfig],
          isGlobal: true,
          expandVariables: true
        }),
        ProductModule,
      ],
      providers: [
        AppConfigService,
        ProductService,
      ],
      controllers: [ProductController]
    }).compile();

    productController = app.get< ProductController >(ProductController);
    productService = app.get< ProductService >(ProductService);
  });

  afterAll(() => jest.restoreAllMocks());

}

标签: mongodbunit-testingjestjsnestjs

解决方案


理论上,任何没有在单元测试中直接测试的东西都应该被嘲笑。在这种情况下,您有两个依赖AppConfigService项 adn DATABASE_CONNECTION。您的单元测试应该提供看起来像注入依赖项的模拟对象,但具有定义且易于修改的行为。在这种情况下,类似这样的东西可能是您正在寻找的

beforeEach(async () => {
  const modRef = await Test.createTestingModule({
    providers: [
      ProductService,
      {
        provide: AppConfigService,
        useValue: {
          appConfigObject: mockConfigObject
        }
      },
      {
        provide: 'DATABASE_CONNECTION',
        useValue: {
          <databaseMethod>: jest.fn()
      }
    ]
  }).compile();
  // assuming these are defined in the top level describe
  prodService = modRef.get(ProductionService);
  conn = modRef.get('DATABASE_CONNECTION');
  config = modRef.get(AppConfigService);
});

在你的控制器测试中,你不应该担心除了ProdctService.

如果您需要更多帮助,这里有大量示例

编辑 2020 年 9 月 4 日

在使用像 Mongo 这样的东西时,模拟链式方法是一个主要的痛点。有几种方法可以解决,但最简单的可能是创建一个模拟对象,例如

const mockModel = {
  find: jest.fn().mockReturnThis(),
  update: jest.fn().mockReturnThis(),
  collation: jest.fn().mockReturnThis(),
  ...etc
}

在链中的最后一个调用中,让它返回预期的结果,以便您的服务可以继续运行其余代码。这意味着如果你有一个像

  const value = model.find().collation().skip().limit().exec()

您需要设置exec()方法以返回您期望的值,可能使用类似的东西

jest.spyOn(mockModel, 'exec').mockResolvedValueOnce(queryReturn);

推荐阅读