如何为@Injectable()mongodb服务编写Nestjs单元测试

时间:2020-09-02 04:22:21

标签: mongodb unit-testing jestjs nestjs

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

database.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 {}

product.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());

}

2 个答案:

答案 0 :(得分:2)

任何未经单元测试直接测试的理论上都应该被嘲笑。在这种情况下,您有两个依赖项,AppConfigServiceDATABASE_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以外的其他任何事物。

如果您需要更多帮助,there's a large repository of examples here

编辑9/04/2020

模拟链接方法是处理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);

答案 1 :(得分:0)

我也在探索将原生 Mongodb 与 NestJS 结合使用。下面是我对 db 中 cron 作业服务更新值的工作测试。

src/cron/cron.service.ts

import { Inject, Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { Db } from 'mongodb';
import { Order } from 'src/interfaces/order.interface';

@Injectable()
export class CronService {
  constructor(
    @Inject('DATABASE_CONNECTION')
    private db: Db,
  ) {}

  @Cron(CronExpression.EVERY_30_SECONDS)
  async confirmOrderEveryMinute() {
    console.log('Every 30 seconds');

    await this.db
      .collection<Order>('orders')
      .updateMany(
        {
          status: 'confirmed',
          updatedAt: {
            $lte: new Date(new Date().getTime() - 30 * 1000),
          },
        },
        { $set: { status: 'delivered' } },
      )
      .then((res) => console.log('Orders delivered...', res.modifiedCount));
  }
}

src/cron/cron.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { Db } from 'mongodb';
import { CronService } from './cron.service';

describe('CronService', () => {
  let service: CronService;
  let connection: Db;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CronService,
        {
          provide: 'DATABASE_CONNECTION',
          useFactory: () => ({
            db: Db,
            collection: jest.fn().mockReturnThis(),
            updateMany: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
          }),
        },
      ],
    }).compile();

    service = module.get<CronService>(CronService);
    connection = module.get('DATABASE_CONNECTION');
  });

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

  it('should confirmOrderEveryMinute', async () => {
    await service.confirmOrderEveryMinute();
    expect(connection.collection('orders').updateMany).toHaveBeenCalled();
  });
});
相关问题