当前位置: 首页 > 知识库问答 >
问题:

西农存根在引擎盖下是如何工作的?

归星驰
2023-03-14

但我对西农在台面下是如何工作仍有一些疑问。我想我是在说Sinon,但这个问题可能适用于所有其他设计为mock/stub/spy的库。

在过去的几年里,我工作最多的语言是Java。在Java中,我使用Mockito来模拟/存根依赖项和依赖项注入。我曾经导入这个类,用@mock注释这个字段,并将这个mock作为param传递给被测试的类。对我来说,很容易看出我在做什么:模仿一个类,并将模仿作为param传递。

当我第一次开始使用SinonJS时,我看到了这样的情况:

moduleundertest.spec.js

const request = require('request')

describe('Some tests', () => {
  let requestStub

  beforeEach(() => {
    requestStub = sinon.stub(request, 'get')
  })

  afterEach(() => {
    request.get.restore()
  })

  it('A test case', (done) => {
    const err = undefined
    const res = { statusCode: 200 }
    const body = undefined
    requestStub
      .withArgs("some_url")
      .yields(err, res, body)

    const moduleUnderTest = moduleUnderTest.someFunction()

    // some assertions
    })
})

moduleundertest.js

const request = require('request')
// some code
  request
    .get("some_url", requestParams, onResponse)

而且管用。当我们运行测试时,实现moduleundertest.js中的request调用request模块的stubbed版本。

我尝试了Sinon repo中stub.js代码中的逻辑,但我对JavaScript还不是很熟悉。抱歉这篇文章太长了,如果这是一个假问题,我很抱歉。:)

共有1个答案

卢黎昕
2023-03-14

如果我们不将stubbed对象作为param传递(注入),Sinon(和其他mock/stub/spy库)如何使实现调用stub?

让我们自己编写简单的stubbing util,好吗?

为了简洁起见,它非常有限,没有提供stubbing API,每次只返回42。但这应该足以说明Sinon是如何工作的。

function stub(obj, methodName) {
    // Get a reference to the original method by accessing
    // the property in obj named by methodName.
    var originalMethod = obj[methodName];

    // This is actually called on obj.methodName();
    function replacement() {
        // Always returns this value
        return 42;

        // Note that in this scope, we are able to call the
        // orignal method too, so that we'd be able to 
        // provide callThrough();
    }

    // Remember reference to the original method to be able 
    // to unstub (this is *one*, actually a little bit dirty 
    // way to reference the original function)
    replacement.originalMethod = originalMethod;

    // Assign the property named by methodName to obj to 
    // replace the method with the stub replacement
    obj[methodName] = replacement;

    return {
        // Provide the stub API here
    };
}

// We want to stub bar() away
var foo = {
    bar: function(x) { return x * 2; }
};

function underTest(x) {
    return foo.bar(x);
}

stub(foo, "bar");
// bar is now the function "replacement"
// foo.bar.originalMethod references the original method

underTest(3);

Sinon在测试执行期间替换整个请求模块(或部分),使存根通过require('request')可用,然后在测试完成后恢复它?

require('request')将返回在每次调用“request”模块中创建的相同的(对象)引用。

请参见NodeJS文档:

模块在第一次加载后缓存。这意味着(除其他外)对require('foo')的每次调用都将返回完全相同的对象,如果它将解析为相同的文件。

多次调用require('foo')可能不会导致多次执行模块代码。这是一个重要的特点。使用它,可以返回“部分完成”的对象,从而允许在传递依赖项会导致循环的情况下加载传递依赖项。

如果它还没有变得清楚:它只替换从“requested”模块返回的对象引用的一个方法,它不会替换该模块。

这就是为什么你不打电话

stub(obj.method)

因为这只传递对函数方法的引用。Sinon将无法更改对象obj

文件还说:

如果您想让一个模块多次执行代码,那么导出一个函数,并调用该函数。

这意味着,如果模块如下所示:

module.exports = function() {
    return {
        // New object everytime the required "factory" is called
    };
};
        // The function returned by require("foo") does not change
const   moduleFactory = require("./foo"),
        // This will change on every call
        newFooEveryTime = moduleFactory();

这样的模块工厂函数不能存根,因为您不能从模块内替换require()导出的函数。

在Java中,我使用Mockito来模拟/存根依赖项和依赖项注入。我曾经导入这个类,用@mock注释这个字段,并将这个mock作为param传递给被测试的类。对我来说,很容易看出我在做什么:模仿一个类,并将模仿作为param传递。

在Java中,您(没有任何东西)不能将一个方法重新分配到一个新的值,这是不能做到的。取而代之的是生成新的字节码,使mock提供与mocked类相同的接口。与Sinon相反,在Mockito中,所有的方法都被嘲笑,并且应该被明确地指示调用真正的方法。

Mockito将有效地调用mock(),并最终将结果分配给带注释的字段。

但是您仍然需要将模拟替换/分配给被测试类中的一个字段,或者将它传递给一个被测试的方法,因为模拟本身没有帮助。

@Mock
Type field;

Type field = mock(Type.class)
var myAPI = { method: function () {} };
var mock = sinon.mock(myAPI);

mock.expects("method").once().throws();
wrapMethod(this.object, method, function () {
    return mockObject.invokeMethod(method, this, arguments);
});
 类似资料:
  • Java的switch语句是如何工作的?它如何将所使用变量的值与案例部分中给出的值进行比较?它是否使用或,还是完全是其他原因? 我主要对1.7之前的版本感兴趣。

  • 除了阅读github中的代码之外,是否有关于signalr.redis包如何工作的白皮书类型的文档?具体地说,我想知道它为Redis添加了哪些键、更新/删除策略等。当查看Redis内部时,我只看到以下调用中指定的一个键(即“signalr.Redis.sample”): 这把钥匙好像是Redis的柜台。我假设正在创建其他键并迅速删除,以方便连接到Redis的每个应用服务器之间的消息。

  • @Component表示给定的类将是给定上下文中的单例,@aspect表示在运行时/编译期间,一个方面类的内容将以某种方式编织到目标类中--例如,这个目标类不是单例而是原型。我最后的结局是什么?

  • 此函数生成数组的排列。我已经把笔放在纸上,在开发工具中放置断点,并煞费苦心地逐步完成每个函数调用,但我仍然不明白这是如何工作的。 具体来说,就是 for 循环。一旦 do It 函数拼接了数组中的所有数字,它将临时数组的切片副本推送到答案数组中。然后,它将项拼接到参数数组中,从临时数组中弹出相同的项,并返回 for 循环第一次迭代的答案。因此,在遍历数组一次后,答案 = [1,2,3] 温度 =

  • 新的Java17型模式匹配开关如何在引擎盖下工作?由于该功能相当新,本问题不讨论它。 提醒:要使此代码在Java 17下工作,需要启用预览功能 使用javap-c对上述代码进行了反汇编: 鉴于这一关键点: 看起来Java在自动生成的int-typeSwitch(object,int)方法中将对象转换为int,我看不到生成的代码。这种转换的目标是能够在int值上使用常规的切换表。 起初,我认为使用了

  • 问题内容: 在阅读了戴夫·切尼(Dave Cheney)关于Go的地图的博客文章之后,对我来说,还有几件事尚不清楚。 TLDR: 为什么它们无序? 实际值存储在哪里? 深入研究运行时程序包后,我发现基本的映射结构如下: -是存储区数组,其中索引是键的哈希值的低位,其中存储区为: ..好吧,这只是每个项目是键的哈希值的第一个字节的数组。键值对存储为(每个存储桶八对)。但是到底在哪里?考虑到映射可能包