TypeError: updatedService.save не является функцией при использовании тестирования API Nest JS с Jests и Mongoose - PullRequest
1 голос
/ 23 апреля 2020

В зависимости от введенного пользователем DTO, информация о продукте может быть обновлена ​​с помощью HTTP PATCH. My Nest JS Служба выглядит следующим образом:


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 - это понедельник goose документ (ProductDoc) возвращается из findProduct:

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)

Как мне преодолеть недоступность .save() в тесте Jest?

Источники:

  1. Я просто копирую Academind Nest JS + MongoDB Tutorial
  2. Поскольку в учебнике нет тестов, я полностью полагаюсь на репозиторий Здесь jmcdo29 / гнездо для тестирования js

РЕДАКТИРОВАТЬ

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

1 Ответ

1 голос
/ 23 апреля 2020

Моя команда столкнулась с той же ошибкой в ​​прошлом месяце!

После поиска лучших практик я нашел простой способ сделать это ...

Я рекомендую использовать *.repository.ts файлы, так что вы можете просто переместить все элементы Mon goose в этот файл, и оставить ваш *.service.spec.ts намного проще и отделенным. Так что эта ошибка никогда не повторится.

Посмотрите на этот пример:

product.repository.ts

Идея состоит в том, чтобы разместить все Mon goose операций в файле репозитория, таких как 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. Наш сервисный файл должен быть максимально скудным и содержать только бизнес логи c.

@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

Убедитесь, что у вас ProductRepository в провайдерах .

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

product.service.spe c .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});
    });
  });
});

Надеюсь, я смогу помочь тебе, приятель!

...