Test - Mocking (Jest)

Junghyun Park·2021년 9월 9일
0

Mock Functions

https://jestjs.io/docs/mock-functions

Mock Funcitions은 기존 Function을 대체하는 가짜 함수를 의미하고, 테스트 대상 코드의 외부 의존성을 끊어 빠르고 쉽게 테스트 할 수 있도록 도와 줌

  • 2 가지 mock function의 방법
  1. 테스트 코드 안에서 mock function을 만드는 법
  2. manual mock을 만들어, 모듈 dependency에 override 하는 방법

Using a mock function

const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);

.mock property

  • 모든 mock 함수는 .mock을 가짐
  • 어떻게 호출되는지, 무슨 함수가 리턴되는지 등에 대한 데이터를 가지고 있음
  • 각 호출의 this를 추적함
// The function was called exactly once
expect(someMockFunction.mock.calls.length).toBe(1);
// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');
// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);
// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toEqual('test');

Mock Return Values

  • test 동안 코드에 test value을 주입할 수도 있음
const filterTestFn = jest.fn();
// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12

Mocking Modules

// users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});

Mocking Partials

  • 모듈 전체를 받아서, 일부 함수에 대해서만 mock fn으로 대체한 후 return하는 방법
// foo-bar-baz.js
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';
//test.js
import defaultExport, {bar, foo} from '../foo-bar-baz';
jest.mock('../foo-bar-baz', () => {
  const originalModule = jest.requireActual('../foo-bar-baz');
  //Mock the default export and named export 'foo'
  return {
    __esModule: true,
    ...originalModule,
    default: jest.fn(() => 'mocked baz'),
    foo: 'mocked foo',
  };
});
test('should do a partial mock', () => {
  const defaultExportResult = defaultExport();
  expect(defaultExportResult).toBe('mocked baz');
  expect(defaultExport).toHaveBeenCalled();
  expect(foo).toBe('mocked foo');
  expect(bar()).toBe('bar');
});

Mock Implementations

  • jest.fn or mockImplementationOnce를 통해, 리턴 값을 구체화하는 것을 넘어, 전체를 mock fn으로 대체할 수 있음
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
// > true
  • mockImplementation method는 다른 모듈로부터 생성되는 mock function의 default 구현내용을 정의하기 위해서 유용함
// foo.js
module.exports = function () {
  // some implementation;
};
// test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');
// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42
  • 서로 다른 결과를 만들어내는 복수의 함수와 같은 mock function의 복잡한 행동을 재생산하기 위해서는 mockImplementationOnce method를 사용할 것
const myMockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true))
  .mockImplementationOnce(cb => cb(null, false));
myMockFn((err, val) => console.log(val));
// > true
myMockFn((err, val) => console.log(val));
// > false
  • .fn() 내에 default를 세팅해놓으면, mockImplementationOnce가 소진되면, 나머지 부터는 default 함수 호출

Mock Names

  • 선택적으로, jest.fn() 대신에 mock fn에 대한 이름을 제공할 수 있음
  • 테스트 결과 report에서 빠르게 mock fn을 구분하기 위해 사용
const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockImplementation(scalar => 42 + scalar)
  .mockName('add42');

Custom Matchers

어떻게 mock fn이 호출되는지를 알아내는 수고를 덜기 위해서, cutom matcher functions가 있음

// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();
// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);
// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);
// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();

기타

jest.fn(implementation)

선택적으로 implementation(함수)를 전달 받아, 새로운 mock fn으로 반환

jest.spyOn(Object, "{구체적인 함수명}")

어떤 객체에 속한 함수의 구현을 가짜로 대체하지 않고, 해당 함수의 호출여부와 어떻게 호출 되었는지를 알아내야하는 경우 사용

profile
21c Carpenter

0개의 댓글