单元测试模块的最佳实践取决于对同一函数的多次调用的结果

时间:2018-04-12 17:19:14

标签: javascript unit-testing testing jest

我想以最孤立的方式测试我所拥有的函数,返回一个已排序的数组,我所具有的函数具有这样的形状:

import factoryishFunction from './someModule'
import { times } from 'ramda';

export function generateArrayWith(nonDeterministicFunction, deterministicFunction, sizeOfArray) {
return times(factoryishFunction(nonDetermisticFunction, deterministicFunction), sizeOfArray)
.sort((a, b) => (a.value< b.value? 1 : -1)) }

所以基本上我调用times调用一个函数n次并将结果放在一个数组中。然后我调用这个factoryishFunction,使用其他传递的函数返回一个具有属性值的对象,然后按后代顺序按值排序。 factoryishFunction的示例如下:

function factoryishFunction(nonDeterministicFunction, deterministicFunction) {
return deterministicFunction(nonDeterministicFunction)
}

其中nonDeterministicFunction可以是例如() => Math.random()而确定性的(value) => value * 2

根据我的理解,由于factoryishFunction正在做其他事情以便让我恢复价值,我应该在测试中嘲笑它以便进行真正独立的单元测试。

由于factoryishFunction的结果取决于我传递给generateArrayWith的函数,我怀疑的是,在我不涉及factoryishFunction的情况下编写测试的最佳方法是什么?行为。

例如我有:

    import generateArrayWith from '../ItsModule';
import factoryishFunctionMock from '../someModule';

jest.mock('../someModule');

beforeAll(() => {
    factoryishFunctionMock .mockClear();
});
describe('My module', () => {
        it('returns the values sorted', () => {
            factoryishFunctionMock .mockImplementation((() => {
                let value = 0;
                return () => ({value: value++});
            })); // Each call in the mockedImplementation will return a different value from zero and up
            const noop = () => {};
            const actual = generateArrayWith(noop, noop, 3);

            expect(actual).toEqual([{value: 2},{value: 1},{value: 0},]);
        });
});

但我觉得通过将noop传递给generateArrayWith,我不能断言这些值,因为它们依赖于传递的函数。我怎么能以更好的方式做到这一点?

1 个答案:

答案 0 :(得分:1)

我认为你的一般方法很好。你可以添加的一件事就是不要为每个noop()做同样的事情,给它两个不同的间谍。然后,您可以断言这些spys每次被称为正确的次数:

it('returns the values sorted', () => {
  factoryishFunctionMock .mockImplementation(((nonDet, det) => {
    nonDet();
    det();
    let value = 0;
    return () => ({value: value++});
  })); 

  const count = 3;
  const nonDetSpy = jest.fn();
  const detSpy = jest.fn();
  const actual = generateArrayWith(nonDetSpy, detSpy, count);

  expect(actual).toEqual([{value: 2},{value: 1},{value: 0},]);
  expect(nonDetSpy).toHaveBeenCalledTimes(count);
  expect(detSpy).toHaveBeenCalledTimes(count);
});

这应该允许您在不直接调用factoryishFunction()的情况下测试函数的所有组件。

这仍然意味着你正在呼唤真实的times()。如果你想让它真正成为一个单元测试,你可以改为模拟times()并确保用factoryishFunctionMock返回值调用它。

import { times } from 'ramda';

jest.mock('ramda'); // mock it to a simple spy in <project_root>/__mocks__/ramda.js

// ...

it('returns the values sorted', () => {
  const factoryishFunctionMockResult = Symbol('RESULT'); // just something to compare against

  times.mockImplementation(() => [{ value: 0 }, { value: 2 }, { value: 1 }];

  factoryishFunctionMock.mockImplementation((() => factoryishFunctionMockResult);

  const count = 3;
  const nonDet = jest.fn();
  const det = jest.fn();
  const actual = generateArrayWith(nonDetSpy, detSpy, count);

  expect(times).toHaveBeenCalledWith(factoryishMethodMockResult, count);
  expect(factoryishMethodMock).toHaveBeenCalledWith(nonDet, det, count);
  expect(actual).toEqual([{value: 2},{value: 1},{value: 0},]);
});