Mock Functions[Jest]

SnowCat·2023년 2월 27일
0

Jest

목록 보기
4/6
post-thumbnail

Using a mock function

  • forEach라는 함수를 테스트하고 싶다.
export function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}
  • 콜백함수의 작동을 확인하고 싶은것이 목적이라면 forEach에 들어가는 콜백을 어디선가 가져오는 대신에, 간단한 목업함수를 작성해 빠르게 테스트를 진행할 수 있다.

const forEach = require('./forEach');

const mockCallback = jest.fn(x => 42 + x);

test('forEach mock function', () => {
  forEach([0, 1], mockCallback);

  expect(mockCallback.mock.calls).toHaveLength(2);
  expect(mockCallback.mock.calls[0][0]).toBe(0);
  expect(mockCallback.mock.calls[1][0]).toBe(1);
  expect(mockCallback.mock.results[0].value).toBe(42);
});

.mock property

  • mock 속성 내에 호출될 당시의 함수의 인스턴스, 인자, context, 결과값 등을 저장함
  • mock 속성으로는 instances, contxts, calls, result가 있음
// 단순히 목업함수 한번 호출
expect(someMockFunction.mock.calls).toHaveLength(1);

// 첫번째 호출의 첫번째 인자의 값
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');

// 첫번째 호출의 두번째 인자의 값
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// 첫번째 호출의 반환값
expect(someMockFunction.mock.results[0].value).toBe('return value');

// 함수가 처음 호출될때의 context(=this)
expect(someMockFunction.mock.contexts[0]).toBe(element);

// 목업함수의 인스턴스화, 즉 new에 의해 목업함수가 불려진 횟수가 2회
expect(someMockFunction.mock.instances.length).toBe(2);

// 첫 인스턴스화 시에 붙어진 이름
expect(someMockFunction.mock.instances[0].name).toBe('test');

// 가장 최근 호출에서의 첫번째 인자의 값
expect(someMockFunction.mock.lastCall[0]).toBe('test');


const myMock1 = jest.fn();
// instantiation
const a = new myMock1();
console.log(myMock1.mock.instances); //[ <a> ]

const myMock2 = jest.fn();
const b = {};
// 목업함수에 특정한 context를 담아 bind
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts); //[ <b> ]

Mock Return Values

  • 목업함수에 특정한 값이 나오도록 지정해줄 수 있음
const myMock = jest.fn();
console.log(myMock()); // undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock()); // 10, "x", true, true

const filterTestFn = jest.fn();


filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

const result = [11, 12].filter(num => filterTestFn(num));

console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12
console.log(result); // [11]
  • api에서 값을 받아오는 함수를 테스트하고 싶다.
import axios from 'axios';

class Users {
  static all() {
    return axios.get('/users.json').then(resp => resp.data);
  }
}

export default Users;
  • 실제 함수를 사용하지 않고, 가짜 데이터를 생성해 mockResolvedValue에 집어넣음으로써 api 호출을 목업화 할 수 있다.
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);
  return Users.all().then(data => expect(data).toEqual(users));
});

Mocking Partials

  • 모듈 중에서 일부만 목업화하고 일부는 실제로 동작시키게도 할 수 있음
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';

import defaultExport, {bar, foo} from '../foo-bar-baz';

jest.mock('../foo-bar-baz', () => {
  const originalModule = jest.requireActual('../foo-bar-baz');

  //bar 함수는 그대로 내비두고, default, 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 내부에 실제 함수 작성
const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val)); // true
// foo.js
module.exports = function () {
  // some implementation
};

//test.js
jest.mock('../foo'); // 모듈을 바로 목업화함
const foo = require('../foo');

// 실제 목업함수 구현
foo.mockImplementation(() => 42);
foo(); //42	
  • 목업함수의 값이 그때그때 달라지는 경우 mockImplementationOnce 메서드 사용
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

// mockImplementationOnce 범위를 벗어나면 jest.fn에 정의된 방식으로 함수가 동작함
const myMockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// 'first call', 'second call', 'default', 'default'
  • 메서드들이 엮여 있어 this를 반환해야 하는경우 mockReturnThis() 메서드 사용
const myObj = {
  myMethod: jest.fn().mockReturnThis(),
};

// 아래 메서드와 동일

const otherObj = {
  myMethod: jest.fn(function () {
    return this;
  }),
};

Mock Names

  • 목업 함수에 이름을 지어주어 테스트에 오류가 발생시 빠르게 디버깅을 진행할 수 있음
const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockImplementation(scalar => 42 + scalar)
  .mockName('add42');

Custom Matchers

  • 목업함수에는 추가로 함수의 호출 횟수, 스냅샷, 선택인자, 이름과 관련된 메서드들이 있음
// 호출이 됬는지 여부를 확인
expect(mockFunc).toHaveBeenCalled();

// arg1, arg2의 인자를 가진 상태로 호출이 됬는지를 확인
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);

// 가장 마지막 호출이 arg1, arg2를 가지고 호출됬는지 확인
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// 모든 호출과 목업 이름이 스냅샷으로 작성됨
expect(mockFunc).toMatchSnapshot();

// 특정 횟수 이상 목업이 호출되는지 확인
expect(mockFunc.mock.calls.length).toBeGreaterThan(3);

// 목업 함수의 호출에서 적어도 배열에 있는 인자중 하나 이상의 인자를 가졌는지 확인
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);

// 목업함수의 이름이 일치하는지 확인
expect(mockFunc.getMockName()).toBe('a mock name');
profile
냐아아아아아아아아앙

0개의 댓글