I want to spy on a sub-function that is exported as a named export, but, it seems like we cannot spy on it.
Let's say I have two functions called add
and multiply
in operations.js
and export them as named exports:
const add = (a, b) => {
return a + b
}
const multiply = (a, b) => {
let result = 0
for (let i = 0; i < b; i++) {
result = add(a, result)
}
return result
}
export { add, multiply }
And the test file uses sinon-chai
to try to spy on the add
function:
import chai, { expect } from 'chai'
import sinon from 'sinon'
import sinonChai from 'sinon-chai'
import * as operations from './operations'
chai.use(sinonChai)
describe('multiply', () => {
it('should call add correctly', () => {
sinon.spy(operations, 'add')
operations.multiply(10, 5)
expect(operations.add).to.have.been.callCount(5)
operations.add.restore()
})
})
The result is
AssertionError: expected add to have been called exactly 5 times, but it was called 0 times
But, if I calls operations.add
directly like the following, it passes the test:
describe('multiply', () => {
it('should call add correctly', () => {
sinon.spy(operations, 'add')
operations.add(0, 5)
operations.add(0, 5)
operations.add(0, 5)
operations.add(0, 5)
operations.add(0, 5)
expect(operations.add).to.have.been.callCount(5)
operations.add.restore()
})
})
It seems like sinon-spy creates a new reference for operations.add
but the multiply
function still uses the old reference that was already bound.
What is the correct way to spy on the add
function of this multiply
function if these functions are named exports?
Generally, how to spy on a sub-function of a tested parent function which both are named exports?
[UPDATE]
multiply
function is just an example. My main point is to test whether a parent function calls a sub-function or not. But, I don't want that test to rely on the implementation of the sub-function. So, I just want to spy that the sub-function is called or not. You can imagine like the multiply
function is a registerMember
function and add
function is a sendEmail
function. (Both functions are named exports.)
I have a workaround for my own question.
Current, the multiply()
function is tightly coupling with add()
function. This makes it hard to test, especially, when the exported functions get new references.
So, to spy the sub-function call, we could pass the sub-function into the parent function instead. Yes, it's dependency injection
.
So, in operations.js
, we will inject addFn
into multiply()
and use it as follows:
const add = (a, b) => {
return a + b
}
const multiply = (a, b, addFn) => {
let result = 0
for (let i = 0; i < b; i++) {
result = addFn(a, result)
}
return result
}
export { add, multiply }
Then, in the test, we can spy on add()
function like this:
describe('multiply', () => {
it('should call add correctly', () => {
sinon.spy(operations, 'add')
operations.multiply(10, 5, operations.add)
expect(operations.add).to.have.been.callCount(5)
operations.add.restore()
})
})
Now, it works for the purpose of testing whether the sub-function is called correctly or not.
(Note: the drawback is we need to change the multiply()
function)