Reputation: 763
I am writing tests with jest in which I want to spy on some lodash functions, that I import separately in the module (opposed to importing the whole lodash module as _
), e.g.
/** matrix.js **/
import shuffle from 'lodash/shuffle'
import pick from 'lodash/pick'
// ...
/**
* Shuffles the order of the rows in the matrix. If a column/variable name
* is specified, only the rows in this column are shuffled.
*
* @export
* @param {array} matrix The matrix to be shuffled
* @param {array} columns Array containing the variable/column to be shuffled
* @returns {array}
*/
export function shuffleVert (matrix, columns) {
if (typeof (columns) === 'undefined' || (isArray(columns) && columns.length === 0)) {
return shuffle(matrix)
} else if (!isArray(columns)) {
throw new TypeError('Invalid argument for columns specified to shuffleVert. Expects an array containing column names')
} else {
let grouped = unstack(matrix)
let cols = pick(grouped, columns)
cols = Object.entries(cols).reduce((prev, [key, values]) => {
prev[key] = shuffle(values)
return prev
}, {})
return stack({ ...grouped, ...cols })
}
The shuffleVert
function shuffles all rows of a matrix, or only those of the specified columns. Because it is difficult to test functions with a random output, to my knowledge, I just want to test if the shuffle and pick function of lodash have been called inside the tested function.
I currently have implemented a working spy procedure in my test module, but I don't think it's conventional or efficient, and I just think there must be a better way to do this...
/* matrix.test.js */
import {
shuffleVert,
} from 'matrix'
/** Generate a mock functions to spy on lodash */
const mockShuffle = jest.fn()
jest.mock('lodash/shuffle', () => a => {
const shuffle = jest.requireActual('lodash/shuffle')
mockShuffle()
return shuffle(a)
})
const mockPick = jest.fn()
jest.mock('lodash/pick', () => (a, b) => {
const pick = jest.requireActual('lodash/pick')
mockPick()
return pick(a, b)
})
describe('reverseRows', () => {
let srcMatrix
beforeEach(() => {
srcMatrix = [
{ number: 1, word: 'one' },
{ number: 2, word: 'two' },
{ number: 3, word: 'three' }
]
mockShuffle.mockClear()
mockPick.mockClear()
})
it('should shuffle the rows of the entire matrix with no argument for columns', () => {
shuffleVert(srcMatrix)
// 2 is weird, but seems correct.
// It appears the shuffle function calls itself recursively
expect(mockShuffle).toHaveBeenCalledTimes(2)
})
it('should only shuffle the rows of columns that were specified', () => {
shuffleVert(srcMatrix, ['word'])
expect(mockShuffle).toHaveBeenCalledTimes(2)
expect(mockPick).toHaveBeenCalledTimes(2)
})
})
I know jest has a spyOn
functionality, but that only appears to work on object methods, thus
import * as lodash from 'lodash'
const shuffleSpy = jest.spyOn(lodash, 'shuffle')
results in the error Cannot spyOn on a primitive value; undefined given
What is generally the best way in jest to spy on methods of a module? Is there a better implementation for doing what I'm trying to achieve? Or is this already the way to go?
Upvotes: 10
Views: 15830
Reputation: 867
Please try this and let me know if it works for you
import { shuffleVert } from 'matrix';
const shuffleVertRef = {
shuffleVert
};
let spyShuffleVert;
beforeEach(() => {
spyShuffleVert = jest.spyOn(shuffleVertRef, "shuffleVert");
}
afterEach(() => {
jest.clearAllMocks();
}
it('should shuffle the rows of the entire matrix with no argument for columns', () = {
let srcMatrix = [
{ number: 1, word: 'one' },
{ number: 2, word: 'two' },
{ number: 3, word: 'three' }
];
shuffleVert(srcMatrix);
expect(spyShuffleVert).toHaveBeenCalledTimes(2);
}
Then if you want to check any other function execution times, just add them to a/that constant and spyOn
them below.
I too don't have a full understanding of this, but from the little I read, the latest versions of JavaScript have this principle called "single source of truth" which is required for some things to work properly.
Upvotes: 3