Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spying on module functions in jest

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?

like image 690
Daniel Schreij Avatar asked Nov 16 '22 20:11

Daniel Schreij


1 Answers

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.

like image 58
Tiramonium Avatar answered Nov 19 '22 10:11

Tiramonium