【问题标题】:Javascript: Mocking Constructor using SinonJavascript:使用 Sinon 模拟构造函数
【发布时间】:2013-01-12 05:34:13
【问题描述】:

我正在努力弄清楚如何使用 sinon 模拟构造函数。我有一个函数,它将通过调用一个接受一些参数的构造函数来创建多个小部件。我想验证构造函数是否使用正确的参数被调用了正确的次数,但我不想实际构造小部件。以下链接似乎解释了模拟构造函数的简单方法,但它对我不起作用:

Spying on a constructor using Jasmine

http://tinnedfruit.com/2011/03/25/testing-backbone-apps-with-jasmine-sinon-2.html

当我对构造函数进行以下调用时:

sinon.stub(window, "MyWidget");

我收到以下错误:

Uncaught TypeError: Attempted to wrap undefined property MyWidget as function 

在 Chrome 中调试时,我看到 MyWidget 显示在 Scope 变量的 Local 部分中,但是窗口中没有 MyWidget 属性。

任何帮助将不胜感激。

【问题讨论】:

  • 请注意,这篇文章没有为所述问题提供任何实际解决方案。

标签: javascript unit-testing constructor mocking sinon


【解决方案1】:

我需要一个解决方案,因为我的代码正在调用 new 运算符。我想模拟新调用创建的对象。

var MockExample = sinon.stub();
MockExample.prototype.test = sinon.stub().returns("42");
var example = new MockExample();
console.log("example: " + example.test()); // outputs 42

然后我使用 rewire 将其注入到我正在测试的代码中

rewiredModule = rewire('/path/to/module.js');
rewiredModule.__set__("Example", example);

【讨论】:

    【解决方案2】:

    来自sinonjs官网:

    用存根函数替换 object.method。调用object.method.restore()可以恢复原来的函数; (或 stub.restore();)。如果属性不是>已经是函数,则会引发异常,以帮助避免在存根方法时出现拼写错误。

    这只是说明您要为其创建存根的函数必须是 object 对象的成员。

    把事情说清楚;你打电话给

    sinon.stub(window, "MyWidget");
    

    MyWidget 函数需要在全局范围内(因为您将 window 作为参数传递)。但是,正如您已经说过的,此函数位于本地范围内(可能在对象字面量或命名空间中定义)。

    在 javascript 中,每个人都可以访问全局范围,但反之则不行。

    检查您声明 MyWidget 函数的位置并将容器对象作为第一个参数传递给 sinon.stub()

    【讨论】:

    • 谢谢 ppoliani。这更有意义。我正在使用 dojo/require.js 并且我注意到 MyWidget 是在全局范围内创建的,但根据我的声明语句进行了命名空间。所以它存在于window.my.namespace.MyWidget 中。我能够在全局范围内成功地模拟/存根它。现在我的问题是在调用 MyWidget 构造函数的对象内部,正在使用本地版本(来自定义)而不是我存根的全局版本。关于如何强制 require.js 识别存根构造函数的任何想法?
    • 如果您使用的是 require.js,那么您是否尝试在“test.js”文件中添加依赖项? /n 例如,require(['ModuleUnderTest'], function(ModuleUnderTest){sinon.stub(ModuleUnderTest,'MyWidget')});。在任何情况下,您都需要加载包含 MyWidget 构造函数的模块。请记住,测试文件与任何其他 javascript 文件一样,它使用相同的机制来定位引用。
    • 再次感谢,但这对我也不起作用。问题是我遵循 AMD 模式,因此依赖项位于闭包中且无法访问。在过去的几天里,我一直在尝试不同的解决方案,包括 Testr.js、Squire.js、StubModule.js,以及我自己基于类似原理的混合物。到目前为止,没有什么对我有用。我认为主要原因之一是它们是为 Require.js 构建的,而我使用的是 dojo(我认为 dojo 的 require 实现略有不同)。 StubModule.js 与 dojo 一起工作,但我未能成功地让它在我的应用程序中工作。
    【解决方案3】:

    使用 Sinon 4.4.2,我可以像这样模拟一个实例方法:

    const testObj = { /* any object */ }
    sinon.stub(MyClass.prototype, "myMethod").resolves(testObj)
    let myVar = await new MyClass(token).myMethod(arg1, arg2)
    // myVar === testObj
    

    此处提供了类似的解决方案: Stubbing a class method with Sinon.js

    【讨论】:

    • 这并没有解决如何模拟类的构造函数的问题。
    【解决方案4】:

    我使用 Mockery 模拟构造函数/函数没有任何问题。

    var mockery = require('mockery');
    var sinon = require('sinon');
    
    mockery.enable({
      useCleanCache: true,
      warnOnReplace: false,
      warnOnUnregistered: false
    });
    
    exports.Client = function() {/* Client constructor Mock */};
    var ClientSpy = sinon.spy(exports, 'Client');
    mockery.registerMock('Client', ClientSpy);
    
    var Factory = require('Factory'); // this module requires the Client module
    

    您应该能够像上面的示例一样应用 Sinon Spy。

    确保在测试后禁用或重置 Mockery!

    【讨论】:

      【解决方案5】:

      使用sinon.createStubInstance(MyES6ClassName),那么当MyES6ClassName 用new 关键字调用时,会返回一个MyES6ClassName 实例的存根。

      【讨论】:

      • 这不会覆盖构造函数。
      【解决方案6】:

      刚刚在documentation.找到这个

      如果您想创建 MyConstructor 的存根对象,但不想调用构造函数,请使用此实用函数。

      var stub = sinon.createStubInstance(MyConstructor)

      【讨论】:

      • 这不会模拟构造函数。顾名思义,它创建了一个存根实例,并且不以任何方式修改构造函数。此答案与问题无关。
      • Plus createStubInstance 在最新版本的诗浓中已被弃用。
      • createStubInstance 自 6.3.4 起尚未被弃用 sinonjs.org/releases/v6.3.4/utils
      【解决方案7】:

      我错误地输入sinon.stub.throws(expectedErr) 而不是sinon.stub().throws(expectedErr) 时遇到了这个错误。我以前犯过类似的错误,之前没有遇到过这个特定的消息,所以它把我扔了。

      【讨论】:

        【解决方案8】:

        用 sinon 模拟和存根构造函数

        我给出了两个解决方案。第一个解决了这个问题,如何用行为来模拟构造函数,第二个展示了如何用一个假人来存根它。 Google 多次引导我回答这个问题,以搜索如何存根。

        用行为模拟构造函数

        我不知道这是否是最短路径。至少它确实如此,所要求的是什么。

        首先,我使用 sinon 的 fake 方法创建一个具有我想要的行为的模拟构造函数 Mock。然后我必须一一添加方法。由于我没有调查的原因,将UnderTest的整个原型设置为Mock时,它不起作用。

        require('chai').should();
        const { fake} = require('sinon');
        
        class UnderTest {
          constructor() {
            this.mocked = false;
          }
        
          isMocked() {
            return this.mocked;
          }
        }
        
        describe('UnderTest', () => {
          let underTest;
          let isMocked;
          before(() => {
            const Mock = fake(function () { this.mocked = true; });
            Mock.prototype.isMocked = UnderTest.prototype.isMocked;
            underTest = new Mock();
            isMocked = underTest.isMocked();
          });
          it('should be mocked', () => {
            isMocked.should.be.true;
          });
        });
        

        用虚拟对象对构造函数进行存根

        如果你被引导到这篇文章,因为你只是想存根构造函数以防止它被执行。

        Sinon 的createStubInstance 创建了一个存根构造函数。它还存根所有方法。因此,被测方法必须先恢复。

        require('chai').should();
        const { createStubInstance } = require('sinon');
        
        class UnderTest {
          constructor() {
            throw new Error('must not be called');
          }
        
          testThis() {
            this.stubThis();
            return true;
          }
        
          stubThis() {
            throw new Error('must not be called');
          }
        }
        
        describe('UnderTest', () => {
          describe('.testThis()', () => {
            describe('when not stubbed', () => {
              let underTest;
              let result;
              before(() => {
                underTest = createStubInstance(UnderTest);
                underTest.testThis.restore();
                result = underTest.testThis();
              });
              it('should return true', () => {
                result.should.be.true;
              });
              it('should call stubThis()', () => {
                underTest.stubThis.calledOnce.should.be.true;
              });
            });
          });
        });
        

        【讨论】:

          【解决方案9】:

          经过一些调整,我能够让StubModule 工作,最值得注意的是在存根模块中需要时将 async:false 作为配置的一部分传递。

          感谢戴维斯先生把这些放在一起

          【讨论】:

          • 该问题要求Sinon解决。为什么会被接受?
          • 我认为 OP 接受了他自己的答案,因为它以他可以接受的方式解决了他的特定问题,但是它没有回答问题,所以我同意这不应该是公认的答案。对于后来想知道 sinon 答案的人(比如我)来说,这不是很有帮助。
          猜你喜欢
          • 2017-11-01
          • 2020-07-11
          • 1970-01-01
          • 2015-12-09
          • 1970-01-01
          • 2013-01-25
          • 2020-08-26
          • 2011-02-27
          • 1970-01-01
          相关资源
          最近更新 更多