My Dream Being Visualized

Jest를 활용한 TDD 단위 테스트 - 8편 (마지막) 본문

Programming/TDD

Jest를 활용한 TDD 단위 테스트 - 8편 (마지막)

마틴킴 2021. 12. 21. 18:20
728x90

 개인 공부를 위한 공간입니다. 틀린 부분 지적해주시면 감사하겠습니다 (_ _)

 

지난 시간에, 기본적으로 Mock 함수의 기본 생성과 axios mock 함수 생성에 대해서 알아보았다.

 

이번 시간엔, 지난 시간의 연장이다.

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

 

Mocking Partials

모듈의 특정 작은 단위만 mock 될 수 있고 남은 모듈의 단위들은 실제 실행부를 가질 수 있다.

// 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', () => {
  // mock 대신 실제 모듈을 가져와서 특정 부분만 분리시킬 수 있는 requireActual
  const originalModule = jest.requireActual('../foo-bar-baz');

  //Mock the default export and named export 'foo'
  return {
  	// esMoudle를 활용할 때 사용하면 된다. (위에 import ~ from ~)
    __esModule: true,
    ...originalModule,
    // default로 export된 함수는 'mocked baz'를 리턴하도록 mock 함수화
    default: jest.fn(() => 'mocked baz'),
    // 기존 함수 사용!
    foo: 'mocked foo',
  };
});

test('should do a partial mock', () => {
  // 부분적으로 mock 함수화 된 부분
  const defaultExportResult = defaultExport();
  expect(defaultExportResult).toBe('mocked baz');
  expect(defaultExport).toHaveBeenCalled();

  // 기존 함수 유지!
  expect(foo).toBe('mocked foo');
  expect(bar()).toBe('bar');
});
한 모듈 내, 특정 함수만 mocking 시 필요하다!

 

Mock Implementations

반환 값을 지정하고 mock 함수 구현을 넘어선 기능을 필요로 할 때가 있다. 이는 jest.fn() 혹은 mock 함수와 함께 mockImplementationOnce 메소드를 사용할 수 있다.
const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// > true

mockImplementation 메소드는 다른 모듈에서 생성된 mock 함수의 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 함수를 재생성 할 때, 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
mockReturnValue 매쳐를 사용하여 값만 반환시킬 때도 있지만,
mock 함수 내 구현부를 재생성 할 때도 있다. 이럴 땐 mockImplementationOnce!

mockImplementationOnce가 다 사용되었다면, jest.fn()에서 default로 선언된 구현부를 실행한다.

const myMockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'

메소드가 엮여있는 경우엔(항상 this를 리턴한다던지), 모든 mock 함수에서 사용할 수 있는 .mockReturnThis() 함수를 사용한다.

const myObj = {
  myMethod: jest.fn().mockReturnThis(),
};

// 위 코드와 아래 코드는 같다! mockReturnThis() 매쳐는 아래를 간단하게 위처럼 구현하게 해준다.

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

 

Mock Names

테스트 에러 출력에서 "jest.fn()" 대신에 보일 mock 함수를 위한 이름을 지을 수 있다.

const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockImplementation(scalar => 42 + scalar)
  .mockName('add42');

 

 

Custom Matchers

마지막으로, mock 함수가 어떻게 몇번 호출되는지 확인을 굳이(?) 하고 싶지 않다면(in order to make it less demanding to assert how mock functions have been called), 커스텀 매쳐들이 있다.

// 최소 한번은 호출된다.
expect(mockFunc).toHaveBeenCalled();

// arg1, arg2와 최소 한번은 호출된다.
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);

// arg1, arg2와 함께 마지막에 호출된다.
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// 모든 호출과 mock 함수의 이름이 스냅샷에 적힌다.
expect(mockFunc).toMatchSnapshot();

.mock 프로퍼티를 inspect하는 일반적인 형태와 달리 편리하게 커스텀하여 호출할 수도 있다.

// 최소 한번 호출된다.
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// 특정 arguments와 같이 최소 한번 호출된다.
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);

// 마지막 호출은 특정 arguments와 같이 호출된다.
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
  arg1,
  arg2,
]);

// 마지막 호출의 첫 argument은 42다.
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);

// 이름 호출
expect(mockFunc.getMockName()).toBe('a mock name');