Weston Weston - 4 months ago 43
Javascript Question

Confusion with mocking stateless React component helper functions with sinon, enyzme, and ES6 imports

I'm currently trying to unit test a React component and have run into some confusion about mocking functions the helper functions. The module looks something like this

export const someHelper = () => {
return ( <div></div> )
}

const MyComponent = () => {
return (
<span>
{someHelper()}
</span>
)
}
export default MyComponent


and then this is what the test file looks like

import chai, { expect } from 'chai'
import chaiEnzyme from 'chai-enzyme'
import sinon from 'sinon'
import { shallow } from 'enzyme'
import React from 'react'

import MyComponent, { someHelper } from './MyComponent'

describe('MyComponent test', function (){
it.only('should call someHelper once', function () {
let spy = sinon.spy(someHelper)
let myComponent = shallow(
<MyComponent />
)
expect(spy.callCount).to.equal(1)
})
})


This test fails however as callCount equals 0. I figured that maybe it had something to do with the context of the
someHelper
function, so I made these changes

export const helpers = {
someHelper () {
...
}
}
const MyComponent = () => {
...
{helpers.someHelper()}
...
}


_

import MyComponent, { helpers } ...

describe(...{
it(...{
let spy = sinon.spy(helpers.someHelper)
...
})
})


But this test still fails with callCount equaling 0. I then made these changes

describe(...{
it(...{
let spy = sinon.spy(helpers, 'someHelper')
...
})
})


And then test now passes.

Why do I have to attach
someHelper
to the
helpers
object for this test to work? And why do I have to use that last
spy(object, 'myfunc')
method when the sinon docs show a
spy(myFunc)
option?

Answer

Why do I have to attach someHelper to the helpers object for this test to work?

Sinon has to be able to replace the reference to the existing function with a spy/stub-wrapped version of it, and it can only do so when that reference is stored in an object (helpers in this case).

It basically does this:

let functionToSpyOn = helpers.someHelper;
let spy = sinon.spy(functionToSpyOn);
helpers.someHelper = spy;

The additional complication here is that the original code has to call the function through this reference, so something like this won't work either:

const someHelper     = () => { ... }
export const helpers = { someHelper }
const MyComponent    = () => {
   ...
   {someHelper()}
   ...
}

The reason being that MyComponent doesn't use the reference that is stored in helpers, which is the one getting replaced by Sinon. That's why the component needs to use helpers.someHelper().

And why do I have to use that last spy(object, 'myfunc') method...

This again has to do with replacing the function with a wrapped version of it.

...when the sinon docs show a spy(myFunc) option?

I find that this idiom is generally of limited use. It doesn't, as you may think, spy on all calls to myFunc unless those calls are made to the spy object that is the result of spy().

For instance:

let callback = (err, result) => { ... }
...
let spy = sinon.spy(callback);
someFuncToTest(spy);
expect(spy.callCount).to.equal(1);

So instead of passing the callback function directly, the spy is passed.

Comments