4

我正在使用 Jest 在 Node.js 应用程序上执行单元测试,其中代码源是用 TypeScript 编写的,然后编译成 JavaScript。

在我希望测试的一个类中,导入了一个外部模块并使用了该模块中的一个方法。我想模拟对这个方法的调用,以便只测试我的代码。

但是,当我运行测试时,出现以下错误:

TypeError: Cannot redefine property: methodName

问题是此方法具有以下对象属性:

{ value: [Function],
  writable: false,
  enumerable: true,
  configurable: false }

configurable: false就是使它成为一个大问题的原因。我无法在模拟调用之前重新定义属性以使其可写。

以下是相关代码的样子:

测试类

import externalType from 'external-module-name';

export class ClassName {
    public propertyName: externalType;

    public method(param: string): Promise<any> {
        return new Promise((resolve, reject) => {
            this.propertyName.externalMethod(param)
            .then((res) => {
                resolve(res);
            })
            .catch((err) => {
                reject(err);
            });
        });
    }
}

单元测试

import { ClassName } from 'path/to/class';

describe('class', () => {
    const class = new ClassName;

    it("Blahblah", (done) => {
        Object.defineProperty(class['propertyName'], 'externalMethod', {writable: true});
        const spy = jest.spyOn(class['propertyName'], 'externalMethod').mockReturnValue(Promise.resolve());
        class.method('string')
        .then((result) => {
            // Various expect()
            done();
        });
    });
});

到目前为止我尝试了什么

  1. 我在测试中添加了以下行:

    Object.defineProperty(class['module'], 'methodName', {writable: true});

  2. 我将我的模拟调用定义如下:

    jest.spyOn(class['module'], 'methodName').mockReturnValue(Promise.resolve());

  3. 我将我的模拟调用定义如下:

    class.propertyName.externalMethod = jest.fn().mockImplementation((query) => { return Promise.resolve(); });

  4. 我试图覆盖我正在调用的属性,如下所示:

    class.propertyName = <any> { externalMethod = (param: any) => { return Promise.resolve(); } }

对于这个,我得到了错误TypeError: Cannot assign to read only property externalMethod of object class,这是有道理的,因为 readable 设置为 false。

但一切似乎都被属性挡住了configurable。我确信可以做一些事情,因为我可能不是唯一一个想要对导入外部模块的类执行单元测试的人,尽管它是安全的。

所以我的问题是:模拟外部方法的干净且有效的方法是什么?如果这是绝对不可能的,那么在不调用该外部方法的情况下测试我的类的方法是什么?

提前致谢!

4

3 回答 3

7

我发现这样做:

jest.mock('path/to/class');

我能够解决这个问题。

于 2020-11-10T22:43:57.233 回答
0

这是一个小技巧,但在我有一个更清洁的解决方案之前,以下是一个可以接受的权宜之计:

let override: boolean | undefined;

static before() {
    Object.defineProperty(module, 'readonlyProperty', {
        get() {
            return this.override;
        }
    });
}

@test
public test() {
    this.override = false;
    expect(module.readonlyProperty).to.be.false;
}

- - 更新 - -

如果使用 sinon 沙箱,请使用replaceAPI

const sandbox = sinon.createSandbox();
sandbox.replace(module, 'readonlyProperty', false);
于 2020-08-04T15:29:57.237 回答
0

这对我有用:

// ========= Mock Object.defineProperty to always allow overriding =========
const originalDefineProperty = Object.defineProperty;
const originalDefineProperties = Object.defineProperties;
Object.defineProperty = (obj, prop, desc) => {
   try {
     return originalDefineProperty(obj, prop, {...desc, configurable: true});
   } catch(e) {
    return originalDefineProperty(obj, prop, desc);
   }
};
Object.defineProperties = (obj, props) => {
  const propsCopy = {...props};
  Object.keys(propsCopy).forEach((key) => {
    propsCopy[key].configurable = true;
  });
  try {
    return originalDefineProperties(obj, propsCopy);
  } catch(e) {
    return originalDefineProperties(obj, props);
  }
};
// =========================================================================

您需要尽快在测试中应用它。如果您使用的是 jest,您可以将此代码片段放在一个 js 文件中,并使用 jest 配置中的“setupFiles”属性加载它。

于 2021-12-30T12:19:09.697 回答