【问题标题】:Unit Testing of functions with method decorator with mocha and sinon使用 mocha 和 sinon 的方法装饰器对函数进行单元测试
【发布时间】:2021-07-28 11:54:18
【问题描述】:

我有一个这样的类方法:

export class MyClass {

        @myDecorator()
        public async createItem(itemId: string, itemOptions: ItemOption[]): Promise<boolean> {

                // ...
                // return await create Item
        }
}

我在另一个文件中定义了这样的装饰器:

export function myDecorator() {
        return function check(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
                const newDescriptor = Object.assign({}, descriptor);
                // do something ...
                return newDescriptor;
        }
}

现在我想对 createItem 方法而不是装饰器进行单元测试,只对方法进行单元测试。但是由于装饰器的作用有点重(其中甚至有数据库访问),我需要模拟它,以便它不会在单元测试中执行。

我在单元测试中使用 mocha 和 sinon,并且已经尝试了以下方法来模拟装饰器:

const decorator = require('./decorator/MyDecorator');
describe('test createItem method', function () {

        it('should return valid result', async function () {
                // 1. try
                decorator.myDecorator = () => sinon.fake();
                // 2. try
                const decoratorStub = sinon.stub(decorator, 'myDecorator');
                decoratorStub.return(true);

                // rest of test 
                const myClass = new MyClass();
                let result = await myClass.createItem(itemId, options);
                expect(result).to.be.equal(true);
        }
}

两次尝试都不成功。无论如何,装饰器都被执行了,但我实际上只是想跳过它。

我怎样才能做到这一点?

【问题讨论】:

    标签: typescript unit-testing mocha.js decorator sinon


    【解决方案1】:

    我回答太晚了 1 小时。 X)

    但我有使用另一个包的替代解决方案:rewire

    我将 console.log 添加到装饰器中以显示假装饰器运行。

    // File: decorator.ts
    export function myDecorator() {
      return function check(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const newDescriptor = Object.assign({}, descriptor);
        const method = newDescriptor.value;
        newDescriptor.value = () => {
          console.log('Real Decorator Run');
          return method.apply(this);
        }
        return newDescriptor;
      }
    }
    

    这是简化的 MyClass。

    // File: MyClass.ts
    import { myDecorator } from './decorator';
    
    export class MyClass {
      @myDecorator()
      public async createItem(): Promise<boolean> {
        console.log('createItem Called');
        return true;
      }
    }
    

    这是测试文件。

    // File: test.ts
    import sinon from 'sinon';
    import { expect } from 'chai';
    import rewire from 'rewire';
    
    import * as decorator from './decorator';
    
    describe('test createItem method', function () {
      it('should return valid result', async function () {
        // 1. Stub myDecorator.
        const decoratorStub = sinon.stub(decorator, 'myDecorator');
        decoratorStub.returns(function check(a, b, c: PropertyDescriptor) {
          const method = c.value;
          c.value = () => {
            console.log('Fake Decorator');
            return method.apply(this);
          }
          return c;
        });
    
        // 2. Rewire MyClass.
        const { MyClass } = rewire('./MyClass');
    
        // 3. Test MyClass.
        const myClass = new MyClass();
        const result = await myClass.createItem();
        expect(result).to.be.equal(true);
      });
    });
    

    当我使用终端运行时:

    $ npx nyc ts-mocha test.ts
    
    
      test createItem method
    Fake Decorator
    createItem Called
        ✓ should return valid result (146ms)
    
    
      1 passing (149ms)
    
    --------------|---------|----------|---------|---------|-------------------
    File          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    --------------|---------|----------|---------|---------|-------------------
    All files     |   52.94 |      100 |      40 |      50 |                   
     MyClass.ts   |     100 |      100 |     100 |     100 |                   
     decorator.ts |   11.11 |      100 |       0 |    12.5 | 3-10              
    --------------|---------|----------|---------|---------|-------------------
    

    【讨论】:

      【解决方案2】:

      您需要使用额外的包proxyquire 来存根从包中导入的独立函数。我们可以使用这个包来存根myDecorator工厂函数。

      例如

      index.ts:

      import { myDecorator } from './decorator/MyDecorator';
      
      interface ItemOption {}
      
      export class MyClass {
        @myDecorator()
        public async createItem(itemId: string, itemOptions: ItemOption[]): Promise<boolean> {
          return true;
        }
      }
      

      ./decorator/MyDecorator.ts:

      export function myDecorator() {
        return function check(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
          const newDescriptor = Object.assign({}, descriptor);
          console.log('real implementation, do heavy and side-effect work');
          return newDescriptor;
        };
      }
      

      index.test.ts:

      import proxyquire from 'proxyquire';
      import sinon from 'sinon';
      
      describe('67413500', () => {
        it('should pass', async () => {
          const myDecoratorStub = sinon.stub().callsFake(() => {
            return function check(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
              const newDescriptor = Object.assign({}, descriptor);
              console.log('fake implementation, skip heavy and side-effect work');
              return newDescriptor;
            };
          });
          const { MyClass } = proxyquire('./', {
            './decorator/MyDecorator': {
              myDecorator: myDecoratorStub,
            },
          });
          const cls = new MyClass();
          const actual = await cls.createItem();
          sinon.assert.match(actual, true);
        });
      });
      

      单元测试结果:

        67413500
      fake implementation, skip heavy and side-effect work
          ✓ should pass (1637ms)
      
      
        1 passing (2s)
      
      --------------------|---------|----------|---------|---------|-------------------
      File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
      --------------------|---------|----------|---------|---------|-------------------
      All files           |   55.56 |      100 |   33.33 |   55.56 |                   
       67413500           |     100 |      100 |     100 |     100 |                   
        index.ts          |     100 |      100 |     100 |     100 |                   
       67413500/decorator |      20 |      100 |       0 |      20 |                   
        MyDecorator.ts    |      20 |      100 |       0 |      20 | 2-5               
      --------------------|---------|----------|---------|---------|-------------------
      

      【讨论】:

        猜你喜欢
        • 2021-09-27
        • 2023-04-09
        • 2019-05-24
        • 2015-05-16
        • 2019-02-14
        • 2017-06-09
        • 2017-07-12
        • 1970-01-01
        • 2021-07-16
        相关资源
        最近更新 更多