使用 mocha 和 sinon 的方法装饰器对函数进行单元测试

如何解决使用 mocha 和 sinon 的方法装饰器对函数进行单元测试

我有一个这样的类方法

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

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

我怎样才能做到这一点?

解决方法

您需要使用额外的包 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,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               
--------------------|---------|----------|---------|---------|-------------------
,

我回答晚了 1 小时。 X)

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

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

// File: decorator.ts
export function myDecorator() {
  return function check(target: any,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');rewire
        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              
--------------|---------|----------|---------|---------|-------------------

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?