【问题标题】:seeding private variable in jest开玩笑地播种私有变量
【发布时间】:2021-06-02 08:58:10
【问题描述】:

我正在像这样在 Nestjs 中测试服务:

@Injectable()
export class ProductsService {
  private readonly products: Map<string, Product> = new Map();

  async create(product: Product): Promise<{ id: string; product: Product }> {
    return new Promise((resolve, reject) => {
      let productAllowed: boolean;
      // do some magic
      // eslint-disable-next-line prefer-const
      productAllowed = true;
      if (!productAllowed) {
        reject(new NotAcceptableException('Such product exists'));
      }
      this.products.set('id', product);
      resolve({
        id: 'id',
        product,
      });
    });
  }

  async findOne(id: string): Promise<Product> {
    return new Promise((resolve, reject) => {
      if (!this.products.has(id)) {
        reject(new NotAcceptableException('No such product exists'));
      }
      resolve(this.products.get(id));
    });
  }
}

我可以测试 create() 并且没关系。 但是在测试 findOne() 时遇到了麻烦:

我需要一个 Product 存在于 products Map 中,以便我可以通过 ID 找到它;但它是空的。所以我需要一种在调用 findOne() 之前以某种方式播种或模拟它的方法:

  describe('findOne()', () => {
    it('should return a product', async () => {
      const result: Product = {
        name: 'product',
        price: 100,
        category: 'junk',
      };

      expect(await service.findOne('id')).toStrictEqual(result);
    });

    it('should throw error on wrong id', async () => {
      const result = new NotAcceptableException('No such product found');
      expect(await service.findOne('not-id')).toThrowError(result);
    });
  });

【问题讨论】:

  • 无论你用 productAllowed 做什么都是一个非常糟糕的想法。
  • 我知道,我是临时加的;例如,我将检查名称是否重复或返回错误而不是重写现有产品

标签: javascript unit-testing jestjs nestjs


【解决方案1】:

我敢打赌,您会感到不舒服,因为您认为在 findOne 单元测试中使用 create 函数是“错误的”。不用担心。

单元测试的意义何在?让您对您的软件按照您认为应该的方式运行感到放心。单元测试不是创建单元并单独测试它们的学术练习,它们是帮助您发布稳定软件的工具。

简而言之,如下所示的测试套件没有任何问题:

  describe('productService', () => {
    it('should create a product', async () => { ... });

    it('should throw an error when creating a disallowed product', async () => { ... });

    it('should retrieve a product', async () => {
       // use create here, it's ok, relax....
       // if you really wanted to, do 2 assertions in here
       // one for the creation and one for the findOne and get rid of the first test
       const { id } = await service.create(...some product);
       const product = await service.findOne(id);
    });

    it('should throw an error when retreiving a product that doesnt exist', async () => { ... });
  });

这 4 个测试(如果您将 createfindOne 结合使用,则为 3 个)测试您的产品服务的全部功能。完成,就这样。

如果出现任何问题,则该测试套件或您的服务需要更新 - 将显示失败,指示“嘿,开发人员 - 去看看产品服务和/或它的测试套件”,您已经实现了所需实现 - 测试套件和服务都非常小(做得好!),很快就能找到错误。

不记得是谁说的:

一套写得不完美的测试经常运行远好于一套写得好的测试从不运行

【讨论】:

  • 谢谢,关于它应该在错误的id上抛出错误的部分,如何检查我想要的错误是否被抛出?
  • @arianpress rejects in jest docs
【解决方案2】:

你的ProductsService 变成有状态的,这不是一件好事。

在目前的情况下,你可以使用一个坏技巧来访问products map - 在 js 中,我们没有 privates 字段:

(service as any).products = new Map([
        ["productId", product],
    ]);

示例:您的测试用例将变为:

    it('should return a product', async () => {
      const result: Product = {
        name: 'product',
        price: 100,
        category: 'junk',
      };

      // this line
      (service as any).products = new Map([
         ["id", product],
      ]);

      expect(await service.findOne('id')).toStrictEqual(result);
    });

我的建议是使用另一个服务作为“缓存”服务,并将服务注入ProductsService 服务。

【讨论】:

  • 所以products服务有状态是不行的,但是缓存服务有状态就可以了?
  • cache service 作为他们的名字,你可以写和读“状态”,行为是不同的。
  • 公平点。这在 DI 风格的框架中确实有意义。相关:当 Java 开发人员尝试构建 JS 框架时,我讨厌它。
  • 还有一点,如果这个答案展示了正确的 DI 风格以及如何使用 DI 对其进行测试,而不是说“这里是如何彻底破解它”,我会给这个答案一个赞成票。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-06-20
  • 2020-07-21
  • 2017-07-01
  • 1970-01-01
  • 2021-01-24
  • 2019-05-24
  • 2018-09-25
相关资源
最近更新 更多