Jest 사용법 - Mock Functions

Jin·2022년 7월 19일
0

jest

목록 보기
4/4
post-thumbnail

Mock functions

1. Using a mock function

  • forEach를 직접 구현한다고 가정해보자.
function forEach(items, callback) {
  for (let index=0; index < items.length; index++){
	callback(items[index])
  }
}

위의 함수를 테스트하기 위해, mock function을 사용 할 수 있다.

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

// mock function은 2번 호출 된다.
expect(mockCallback.mock.calls.length).toBe(2);
// 처음 호출 되는 함수의 첫번째 요소는 0이다.
expect(mockCallback.mock.calls[0][0]).toBe(0);
// 두번째로 호출 되는 함수의 첫번째 요소는 1이다.
expect(mockCallback.mock.calls[1][0]).toBe(1);
// 처음 호출 되는 함수의 결과 값은 42이다.
expect(mockCallback.mock.results[0].value).toBe(42);

2. .mock property

  • 모든 mock function 은 .mock property를 가지고 있다.
  • .mock property는 mock 함수가 어떻게 호출 되었는지, 어떤 값을 return 하는지 등의 정보를 가지고 있다.
  • .mock property는 매 호출 시 this 값도 가지고 있다.
const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);

3. Mock Return Values

  • Mock functions은 return 값을 테스트 중간에 주입 가능하다.
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
  • Mock functions 연속적으로 값을 전달하는 스타일의 코드에 효율적이다.
  • 이러한 스타일은 복잡하게 실제 함수를 직접 수정할 필요가 없다.
const filterTestFn = jset.fn();
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

4. Mocking Modules

  • axios를 사용하여 api로 부터 정보를 가지고 오는 클래스가 있다고 가정해보자.
  • 이 코드를 테스트하기 위해 진짜로 API를 호출하는것이 아닌, axios module을 자동적으로 mock하는 jest.mock(...) 기능을 사용해보자
// users.js
// 실제 구현된 API 호출 모듈 
import axios from 'axios';

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

export default Users;

// users.test.js
// mock 으로 구현된 모듈을 이용한 테스트

import axios from 'axios'
import Users from './users'

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name:'Bob'}];
  const res = {data:users};
  axios.get.mockResolvedValue(res);
  
  return Users.all().then(data => expect(data).toEqual(users));
  // or 
  const result = await Users.all();
  expect(result).toEqual(users);
});

5. Mocking Partials

  • 모듈의 한 부분이 mocked 되면서, 모듈의 나머지 부분도 실제 실행이 가능하다.
    (Subsets of a module can be mocked and the rest of the module can keep their actual implementation)
//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');  
  
  return {
    __esModule: true,
    ...originalModuel,
    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(); 
  // tohaveBeenCalled() : mock function 이 최소 한번은 호출 되었는지 확인 하는것.
  
  expect(foo).toBe('mocked foo');
  expect(bar()).toBe('bar');
});

6. Mock Implementations

  • 테스트를 위해 간단한 구현체를 만들어서 해당 함수를 대체
  • jest.fn, mockImplementation 을 사용 하여 구현 가능하다.
const mockFn = jest.fn(cb => cb(null,true));
mockFn((err,val) => console.log(val));
// true
  • mockImplementation은 다른 모듈에서 생긴 mock function을 정의 할때 유용하다.
// foo.js
module.exports = function(){
  //...
};

// test.js
jest.mock('../foo'):
const foo = require('../foo');

// foo is mock function
foo.mockImplementation(() => 42);
foo();
// 42
  • 만약 다른 결과를 가지는 복잡한 행동을 가진 mock function 을 다시 만들어야한다면 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로 정의한 수보다 더 많이 mock function을 실행 시키면, 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를 return하는 경우 .mockReturnThis() 를 사용 할수 있다.
const myObj = {
  myMethod: jest.fn().mockReturnThis(),
};

// 아래와 같다.

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

7. Mock Names

  • mock 함수에 이름을 지정하여 에러 발생 시 명확하게 알수 있다.
test('test dispaly mock fucntion name', () => {
  const myMock = jest.fn().mockName('nameMock')
  //myMock();
  expect(myMock).toHaveBeenCalled();
});

// 이름 지정한 경우 expect(nameMock).toHaveBeenCalled()
// 이름 지정 안한 경우 expect(jest.fn()).toHaveBeenCalled()

8. Custom Matchers

  • .mock property를 통해 직접 구현이 가능.
  • 편의를 위해 제공 되는 Matchers 사용도 가능

//mock function 이 최소 1번은 호출 됨
expect(mockFunc).toHaveBeenCalled();
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// mock function이 최소 한번은 arg요소들과 호출 됨
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);

// 마지막으로 호출된 mock Function이 특정 arg 요소들과 함게 호출됨
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
  arg1,
  arg2,
]);

// 모든 호출과 mock의 이름이 snapshot에 쓰여진과 같음
expect(mockFunc).toMatchSnapshot();
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.getMockName()).toBe('a mock name');

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

profile
내가 다시 볼려고 작성하는 블로그. 아직 열심히 공부중입니다 잘못된 내용이 있으면 댓글로 지적 부탁드립니다.

0개의 댓글