简体   繁体   中英

Sinon - Can I temporarily mock a method on a stub?

I'm retrofitting unit tests to legacy code, and have setup sinon and proxyquire to inject stubs for dependencies.

In some tests I need to verify that methods on one or more dependencies were called correctly, while allowing all other method calls on the same objects to behave like stubs (returning default values, rather than passing the calls to the real implementations.

So far I've tried a number of approaches, and occasionally have gotten it to seemingly work until I do a little code cleanup, and things again break (when it works the code is in very poor shape--so while it seems like its working it's unclear if it will continue to work or what code is actually having the desired effect).

Below is what I'm currently trying, with comments about my intentions.

const proxyquire = require('proxyquire')
const stubUtils = stub(require('./utils'))
const stubService = stub(require('./service'))

// Setup the SuT to default to talking to stubs
const systemUnderTest = proxyquire('./index', {
    './utils': stubUtils,
    './service': stubService
})

let sandbox
describe('index.doSomething()', () => {
    beforeEach(() => {
        // I'm attempting to revert any test-specific setup and put the dependencies back to default stubs
        sinon.reset();

        // the legacy code is configured by environment variables, between tests I want to reset process.env since each test requires a different configuration
        sandbox = sinon.createSandbox()
        sandbox.stub(process, 'env').value({})

        // someMethod() is printed in a bunch of logs which call .length on it, so my baseline setup needs to configure it
        stubService.someMethod.returns('')
    })

    afterEach(() => {
        // tests *should* call their own .verify(), but I'm assuming adding it here will catch it if the tests miss it
        sinon.verify()
        sandbox.restore()
    })

    // There are several basic "modes" for the code under test
    describe('Scenario A', () => {
        beforeEach('Scenario A Setup', () => {
            // Each scenario sets a few common settings
            process.env.SOME_CONFIG_VALUE = 'mode A'
            // ...
        })

        it('uses the environment variable appropriately', () => {
            // Here's where I'm struggling
            // In this one test I need to verify a call is made with the correct argument on an object that is otherwise behaving like a stub
            const expected = "the expected value"
            process.env.IMPORTANT_VALUE = expected

            // throws: stubUtils.restore is not a function
            //stubUtils.restore()

            // throws: TypeError: Attempted to wrap someMethod which is already wrapped
            const mockCall = sinon.mock(stubUtils).expects('someMethod').withArgs(expected)

            systemUnderTest.doSomething()

            mockCall.verify()
        })

        // it(...  // more tests, similarly needing to mock various methods on the stubs
    })

    // describe('Scenario B', () => {
    // ... and so on.  There are several scenarios, each with several unit tests

I figured it out. The root of the issue I was running into was that I was stub() 'ing an entire object and expecting to be able to .restore() it as well.

However, the .restore() method is still found on the object's methods and not the object itself.

This is an example of my mistake:

const myObj = { doSomething: () => {} }
const stubObj = sinon.stub(myObj)
stubObj.restore()

And here it is fixed by moving the .restore() call to the function :

const myObj = { doSomething: () => {} }
const stubObj = sinon.stub(myObj)
stubObj.doSomething.restore()

PS...

I also found that I got the best test output by sticking with stubs. Previously I thought I needed to use a mock() and .expects followed by a .verify() . That works, and it includes the details of what parameters were mismatched.

However, I found the output of sinon.assert.calledWithMatch(stubObj.doSomething, expected) to be the easiest to read and spot exactly why it failed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM