Javascript 开玩笑地播种私有变量

Javascript 开玩笑地播种私有变量,javascript,unit-testing,jestjs,nestjs,Javascript,Unit Testing,Jestjs,Nestjs,我正在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((reso

我正在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));
    });
  }
}
  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 () => { ... });
  });

您的
ProductsService
变得有状态,这不是一件好事

在当前情况下,您可以使用一个坏把戏来访问
产品
地图-在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
服务。

您的
ProductsService
变得有状态,这不是一件好事

在当前情况下,您可以使用一个坏把戏来访问
产品
地图-在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
服务。

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

单元测试的意义是什么?让您的软件以您认为应该的方式运行,从而让您放心。单元测试不是创建单元并单独测试它们的学术练习,它们是帮助您发布稳定软件的工具

简而言之,这样的测试套件没有什么问题:

@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));
    });
  }
}
  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个测试(如果将
create
findOne
结合起来,则为3个)测试产品服务的整个功能。好了,就这样

如果出现任何问题,那么要么该测试套件或您的服务需要更新-故障将显示为“嘿,开发人员-去看看产品服务和/或它的测试套件”,并且您已经实现了您需要实现的目标-测试套件和服务都非常小(做得很好!),很快就会发现错误

不记得是谁说的:

经常运行的一组编写不完善的测试要比从未运行过的一组编写完善的测试好得多


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

单元测试的意义是什么?让您的软件以您认为应该的方式运行,从而让您放心。单元测试不是创建单元并单独测试它们的学术练习,它们是帮助您发布稳定软件的工具

简而言之,这样的测试套件没有什么问题:

@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));
    });
  }
}
  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个测试(如果将
create
findOne
结合起来,则为3个)测试产品服务的整个功能。好了,就这样

如果出现任何问题,那么要么该测试套件或您的服务需要更新-故障将显示为“嘿,开发人员-去看看产品服务和/或它的测试套件”,并且您已经实现了您需要实现的目标-测试套件和服务都非常小(做得很好!),很快就会发现错误

不记得是谁说的:

经常运行的一组编写不完善的测试要比从未运行过的一组编写完善的测试好得多


因此,产品服务不允许有状态,但缓存服务允许有状态?
缓存服务
作为其名称,您可以写入和读取“状态”,行为不同。公平点。这在DI风格的框架中是有意义的。相关报道:我讨厌Java开发人员尝试构建JS框架。还有一点,如果它展示了一种合适的DI风格以及如何使用DI测试它,我会给这个答案一次投票,而不是说“这是如何彻底破解它”。因此,产品服务没有状态是不好的,但是缓存服务可以是有状态的吗?
缓存服务
作为它们的名称,您可以写入和读取“状态”,行为不同。公平点。这在DI风格的框架中是有意义的。相关报道:我讨厌Java开发人员尝试构建JS框架。还有一点,如果它展示了一种合适的DI风格以及如何使用DI测试它,我会给这个答案投赞成票,而不是说“这是如何彻底破解它”。无论你用
productAllowed
做什么都是一个非常糟糕的想法。我知道,我临时添加了它;例如,我要检查名称是否重复,或者返回一个错误,而不是重写现有的产品无论你用
productAllowed
做什么都是一个非常糟糕的主意;例如,我要检查名称是否重复或其他,以返回错误,而不是重写现有产品谢谢,关于它应该在错误id上抛出错误的部分,如何检查我想要的错误是否被抛出?@arianpress谢谢,关于它应该在错误id上抛出错误的部分,如何检查是否抛出了我想要的错误?@arianpress