TypeError:在使用 Jests 和 Mongoose 测试 NestJS API 时,updatedService.save 不是一个函数

Posted

技术标签:

【中文标题】TypeError:在使用 Jests 和 Mongoose 测试 NestJS API 时,updatedService.save 不是一个函数【英文标题】:TypeError: updatedService.save is not a function when using testing NestJS API with Jests and Mongoose 【发布时间】:2020-08-07 00:02:08 【问题描述】:

根据用户输入的 DTO,可以使用HTTP PATCH 更新产品信息。我的 NestJS 服务如下:


async updateAProduct(product: ProductDTO ) 
        const updatedProduct = await this.findProduct(product.id);
        if (product.title) 
            updatedProduct.title = product.title;
        
        if (product.description) 
            updatedProduct.description = product.description;
        
        if (product.price) 
            updatedProduct.price = product.price;
        
        updatedProduct.save()
    

ProductDTO 是一个接口:

export interface ProductDTO 
    id?: string;
    title?: string;
    description?: string;
    price?: number;

updatedProduct 是从 findProduct 返回的 Mongoose 文档 (ProductDoc):

import  Document  from 'mongoose';

export interface ProductDoc extends Document 
    id: string;
    title: string;
    description: string;
    price: number;   

updateAProduct 服务在控制器中调用如下:

    @Patch('/update/:id')
    async updateAProduct(@Param('id') id: string, @Body() product: ProductDTO) 
        product.id = id;
        await this.productService.updateAProduct(product);
        return null;
    

在写products.service.spec.ts时,我写了以下测试:


describe('ProductsService', () => 
  let service: ProductsService;
  let model: Model<ProductDoc>;

  beforeEach(async () => 
    const module: TestingModule = await Test.createTestingModule(
      providers: [
        ProductsService,
        
          provide: getModelToken('Product'),
          useValue: 
            new: jest.fn().mockResolvedValue(mockProduct()),
            constructor: jest.fn().mockResolvedValue(mockProduct()),
            findById: jest.fn(),
            find: jest.fn(),
            findOne: jest.fn(),
            update: jest.fn(),
            create: jest.fn(),
            remove: jest.fn(),
            exec: jest.fn(),
          ,
        ,
      ],
    ).compile();

    service = module.get<ProductsService>(ProductsService);
    model = module.get<Model<ProductDoc>>(getModelToken('Product'));
  );

  it('should update a product sucessfully', async() => 
    
    jest.spyOn(model, 'findById').mockReturnValue(
      exec: jest.fn().mockResolvedValueOnce(
        mockProductDoc(id: 'uuid1', title: 'Title1', description: 'Description1', price: 50.99)
      ),
     as any);
    const updatedProduct = await service.updateAProduct(
      id: 'uuid1',
      title: 'New Title',
      price: 200.00
    );

    expect(updatedProduct).toEqual(mockProduct('uuid1', 'New Title', 'Description1',200.00));
  );

我的测试失败如下:


 FAIL  src/products/products.service.spec.ts (18.693s)
  ● ProductsService › should update a product sucessfully

    TypeError: updatedProduct.save is not a function

      49 |             updatedProduct.price = product.price;
      50 |         
    > 51 |         updatedProduct.save()
         |                        ^
      52 |     
      53 | 
      54 |     async deleteAProduct(prodID: string) 

      at ProductsService.updateAProduct (products/products.service.ts:51:24)

如何克服 Jest 测试中不可用的.save()

来源:

    我只是在复制Academind NestJS + MongoDB Tutorial 由于教程中没有测试,我完全依赖 Repository Here jmcdo29/testing-nestjs

编辑

findProduct 服务内


  private async findProduct(productID: string): Promise<ProductDoc> 
        let product;
        try 
            product = await this.productModel.findById(productID).exec();
         catch(error) 
            throw new NotFoundException('Could Not Find Product for given ID.');
        
        if (!product) 
            throw new NotFoundException('Could Not Find Product for given ID.');
        
        return product;
    

【问题讨论】:

你模拟的findProduct() 必须返回一个像...mockedObject, save: jest.fn() 这样的对象。我希望你明白吗?! 刚刚意识到。我现在正在尝试。 @MoazzamArif 没有,即使我添加了save: jest.fn() 这个应该可以,但是findProduct(product.id)这个是怎么实现的呢? @MoazzamArif 我用findProduct编辑了这个问题 【参考方案1】:

我的团队上个月遇到了同样的错误!

在搜索了最佳实践之后,我找到了一种简单的方法......

我建议使用 *.repository.ts 文件,这样您就可以简单地将所有 Mongoose 的东西移动到该文件中,而让您的 *.service.spec.ts 更加简单和解耦。所以这个错误永远不会再发生了。

看看这个例子:

product.repository.ts

想法是将所有Mongoose操作放入存储库文件中,例如update()、delete()、find()、populate()、aggregate()、save()...

@Injectable()
export class ProductRepository 
    constructor(@InjectModel('Product') private readonly model: Model<Product>) 

    async findProduct(id: string): Promise<Product> 
        return await this.model.findOne(_id: id).exec();
    

    async save(doc: any): Promise<Product> 
        return await new this.model(doc).save();
    

product.service.ts

这里不要使用@InjectModel,而是注入ProductRepository。我们的服务文件应该尽可能精简,并且只包含业务逻辑。

@Injectable()
export class ProductService 
  constructor(private readonly repository: ProductRepository) 

    async updateAProduct(product: ProductDTO) 
        const updatedProduct = await this.repository.findProduct(product.id);
        if (product.title) 
            updatedProduct.title = product.title;
        
        if (product.description) 
            updatedProduct.description = product.description;
        
        if (product.price) 
            updatedProduct.price = product.price;
        
        await this.repository.save(updatedProduct);
    

product.module.ts

确保您在providers中有ProductRepository

@Module(
  imports: [MongooseModule.forFeature([ name: 'Product', schema: ProductSchema ])],
  controllers: [ProductController],
  providers: [ProductService, ProductRepository],
  exports: [ProductService],
)
export class ProductModule 

product.service.spec.ts

** 而不是使用getModelToken('Product') 替换为ProductRepository

const mockProductRepository = 
    findProduct: jest.fn(),
    save: jest.fn(),
;

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

  beforeAll(async () => 
    const module: TestingModule = await Test.createTestingModule(
      providers: [
        ProductService,
        
          provide: ProductRepository,
          useValue: mockProductRepository,
        
      ],
    ).compile();

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

  describe('Update a product', () => 
    it('should update a product sucessfully', async () => 
        const findProductStub = id: 'uuid1', title: 'Title1', description: 'Description1', price: 50.99;
        mockProductRepository.findProduct.mockResolvedValue(findProductStub);
        const saveProductStub = id: 'uuid1', title: 'New Title', description: 'Description1', price: 200.00;
        mockProductRepository.save.mockResolvedValue(saveProductStub);
        const productToUpdateDto = id: 'uuid1', title: 'New Title', description: 'Description1', price: 200.00;
        const result = await service.updateAProduct(productToUpdateDto);
        expect(result).toEqual(id: 'uuid1', title: 'New Title', description: 'Description1', price: 200.00);
    );
  );
);

希望我能帮助你交配!

【讨论】:

这是一个很好的解决方案。您是否经常在存储库中转移其他 Mongoose CRUD 功能,例如 create()find().exec() 是的,想法是将所有Mongoose操作放入存储库文件中,例如updateOne()deleteOne()find()findOne()findById()populate()、@987654339 @、save() 等等……我们的服务文件应该尽可能精简,并且只包含业务逻辑。谢谢老哥! 我建议使用 mockingoose 来模拟 db CRUD。它还将在模拟返回值时保留猫鼬模式

以上是关于TypeError:在使用 Jests 和 Mongoose 测试 NestJS API 时,updatedService.save 不是一个函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在sql中将dd-mm-yyyy转换为DD-MON-yyyy

CEPH客户端权限管理和授权流程

Ceph MON监视器节点配置参考

TypeError: 'int' 对象在使用 reduce 和 lambda 时不可调用

使用敲除和微风时“TypeError:对象不是函数”

如何将字符串 - 1year 6mon 转换为数字 1.5?