How does Sinon butts work under the hood?

In recent months, I have been working with JavaScript and used SinonJS to drown out some kinds of behavior. I managed to get it working, I skipped a lot of methods, and everything works fine.

But I still have some questions about how Sinon works under the table. I think I'm telling Sinon, but this question can be applied to all other libraries designed for bullying / stub / spy.

The language I have been working on the most in recent years has been Java. In Java, I used Mockito to bully / stub dependencies and inject dependencies. I used to import a class, annotate the field with @Mockand pass this layout as a parameter to the tested class. It's easy for me to understand what I'm doing: mock the class and skip the layout as a parameter.

When I first started working with SinonJS, I saw something like this:

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)

And it works. When we run the tests, requestinside the implementation it moduleUnderTest.jscalls an enveloped version of the module request.

My question is: why does this work?

, request. Sinon ( mock/stub/spy) , ( )? Sinon request ( ) , require('request'), ?

stub.js Sinon repo, JavaScript, , .:)

+4
1

Sinon ( mock/stub/spy) , ( )?

, ?

, API- 42 . , , .

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 module ( ) , require('request'), ?

require('request') (), "", , .

. NodeJS:

. ( ), require('foo') , , .

require('foo') . . " ", , .

: , "" , .

stub(obj.method)

method. Sinon obj.

:

, , .

, :

foo.js

module.exports = function() {
    return {
        // New object everytime the required "factory" is called
    };
};

main.js

        // The function returned by require("foo") does not change
const   moduleFactory = require("./foo"),
        // This will change on every call
        newFooEveryTime = moduleFactory();

factory , , require() .


Java Mockito / . , @Mock . , : .

Java, () , . -, , , . , Mockito , .

Mockito mock() , , .

/ , .

@Mock
Type field;

Type field = mock(Type.class)

mocks

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

mock.expects("method").once().throws();

expects():

wrapMethod(this.object, method, function () {
    return mockObject.invokeMethod(method, this, arguments);
});
+3

Source: https://habr.com/ru/post/1680822/


All Articles