티스토리 뷰

Tool/jest

[번역] Jest - Mock Functions

코딩산책 2018. 11. 5. 06:31

버전: Jest 23.6
날짜: 2018-11-06
※ 내용숙지가 안된 번역이 많아, 원문을 함께 둠

Mock Functions

Mock functions make it easy to test the links between code by erasing the actual implementation of a function, capturing calls to the function (and the parameters passed in those calls), capturing instances of constructor functions when instantiated with new, and allowing test-time configuration of return values.

There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.

Mock 함수는 실제 구현 함수를 소거하고, 함수 호출을 캡처하고(해당 호출에서 전달될 파라미터들과), new로 인스턴스화 할때 생성자 함수의 인스턴스를 캡처 그리고 리턴 밸류의 테스트타임 구성을함으로써 코드간의 링크 테스트하는 것을 쉽게 만든다.

Using a mock function

Let's imagine we're testing an implementation of a function forEach, which invokes a callback for each item in a supplied array.

제공된 배열에서 각각의 아이템에 대한 콜백을 호출 하는 forEach함수의 구현을 테스트하는 것을 생각해 보자.

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

To test this function, we can use a mock function, and inspect the mock's state to ensure the callback is invoked as expected.

이 함수를 테스트하기 위해서 mock 함수를 사용할 수 있고 mock의 state를 검사하여 콜백이 기대하는데로 호출되는지 확인 할 수 있다.

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

All mock functions have this special .mock property, which is where data about how the function has been called and what the function returned is kept. The .mock property also tracks the value of this for each call, so it is possible to inspect this as well:

모든 mock 함수는 .mock 라는 특별한 프로퍼티를 가지며, 이 속성은 함수가 호출된 방식과 리턴된 함수가 보관된 데이터를 저장한다. 

const myMock = jest.fn();

const a = new myMock();
const b = {};
const bound = myMock.bind(b);
bound();

console.log(myMock.mock.instances);
// > [ <a>, <b> ]

These mock members are very useful in tests to assert how these functions get called, instantiated, or what they returned:

이 mock 멤버는 테스트에서 함수가 어떻게 호출되는지, 인스턴스화되는지, 무엇이 리턴되는지 매우 유용한다.

// 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

Mock functions can also be used to inject test values into your code during a test:

테스트 하는 동안 Mock 함수들은 테스트 값들을 너의 코드에 주입하기 위해 사용되어질 수 있다:

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 are also very effective in code that uses a functional continuation-passing style. Code written in this style helps avoid the need for complicated stubs that recreate behavior of the real component they're standing in for, in favor of injecting values directly into the test right before they're used.

Mock 함수들은 함수 연속성 전달 스타일의 코드에서 또한 매우 유용한다. 이런 스타일로 쓰여진 코드는 실제 컴포넌트의 동작을 재현하는 복잡한 스텁이 필요하지 않으므로 값을 사용하기 전에 직접 값을 테스트에 주입할 수 있다.

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(filterTestFn);

console.log(result);
// > [11]
console.log(filterTestFn.mock.calls);
// > [ [11], [12] ]

Most real-world examples actually involve getting ahold of a mock function on a dependent component and configuring that, but the technique is the same. In these cases, try to avoid the temptation to implement logic inside of any function that's not directly being tested.

대부분 실제 예제들은 실제적으로 의존 컴포넌트에 대한 mock 함수를 얻는것과 그것을 구성하는 것을 포함하지만 기술은 동일하다. 이러한 경우 테스트를 통하지 않고 로직을 직접 구현하려는 유혹을 피하자.

Mocking Modules

Suppose we have a class that fetches users from our API. The class uses axios to call the API then returns the data attribute which contains all the users:

API로부터 사용자를 가져오는 클래스가 있다고 가정해보자. 모든 유저를 포함한 data 속성을 리턴하는 API를 호출하기 위해서 그 클래스는  axios 를 사용한다.

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

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

export default Users;

Now, in order to test this method without actually hitting the API (and thus creating slow and fragile tests), we can use the jest.mock(...) function to automatically mock the axios module.

이제, 실제로 API를(느리고 깨지기 쉬운 테스트를 생성하는 것 같은) 히팅(?요청?)하는거 없이 메소드를 테스트하기 위해서, axios 모듈을 자동적으로 mock 하기 위해 jest.mock(...) 함수를 사용할 수 있다.

Once we mock the module we can provide a mockResolvedValue for .get that returns the data we want our test to assert against. In effect, we are saying that we want axios.get('/users.json') to return a fake response.

일단 모듈을 mock 하면 테스트가 원하는 데이터가 리턴되는.get 에 대한 mockResolvedValue 를 제공할 수 있다. 이 영향으로 axios.get('/users.json')가 거짓 응답을 리턴하기를 원한다고 말하고 있습니다.

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

jest.mock('axios');

test('should fetch users', () => {
  const resp = {data: [{name: 'Bob'}]};
  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(users => expect(users).toEqual(resp.data));
});

Mock Implementations

Still, there are cases where it's useful to go beyond the ability to specify return values and full-on replace the implementation of a mock function. This can be done with jest.fn or the mockImplementationOnce method on mock functions.

여전히, 리턴값을 지정할 수 있는 기능을 넘어서 유용한 경우가 있다 그리고 mock 함수의 구현을 바꿉니다. 이것은 mock 함수에서  jest.fn 또는  mockImplementationOnce 메소드를 사용하여 수행할 수 있습니다. 


const myMockFn = jest.fn(cb => cb(null, true));

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

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

The mockImplementation method is useful when you need to define the default implementation of a mock function that is created from another module:

mockImplementation 메소드는 다른 모듈로 부터 생성된 mock 함수의 기본 구현을 정의해야 할때 유용하다.

// 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

When you need to recreate a complex behavior of a mock function such that multiple function calls produce different results, use the mockImplementationOnce method:

멀티플 함수 같은 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

When the mocked function runs out of implementations defined with mockImplementationOnce, it will execute the default implementation set with jest.fn (if it is defined):

mocked 함수가 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'

For cases where we have methods that are typically chained (and thus always need to return this), we have a sugary API to simplify this in the form of a .mockReturnThis() function that also sits on all mocks:

일반적으로 체인되어(그리고 항상 this 을 반환할 필요가 있는) 있는 함수를 갖고 있기 때문에 모든 mocks에 있는 .mockReturnThis() 함수의 형태로 이것을 단순화하는 달콤한 API를 가지고 있다.

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

// is the same as

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

Mock Names

You can optionally provide a name for your mock functions, which will be displayed instead of "jest.fn()" in test error output. Use this if you want to be able to quickly identify the mock function reporting an error in your test output.

옵션으로 mock 함수의 이름을 제공한다, 그것은 에러 출력에서 "jest.fn()" 대신 나타나게 될 것이다. 너의 테스트 출력물에서 에러 리포팅하는 mock 함수를 신속하게 알아보기를 원한다면 이 것을 사용해라.

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

Custom Matchers

Finally, in order to make it simpler to assert how mock functions have been called, we've added some custom matcher functions for you:

마지막으로 mock 함수가 어떻게 호출되었는지 간단히 설명하기 위해서 사용자 matcher 함수를 추가 했다:

// The mock function was called at least once
expect(mockFunc).toBeCalled();

// The mock function was called at least once with the specified args
expect(mockFunc).toBeCalledWith(arg1, arg2);

// The last call to the mock function was called with the specified args
expect(mockFunc).lastCalledWith(arg1, arg2);

// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();

These matchers are really just sugar for common forms of inspecting the .mock property. You can always do this manually yourself if that's more to your taste or if you need to do something more specific:

이런 matchers는 .mock 프로퍼티를 검사하는 일반적인 형식의 좋은방법이다. 더욱 세부적이어야 하거나 더 많은 테스트를 해야한다면 언제든 직접 수동적으로 수행할 수 있다:

// The mock function was called at least once
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// The mock function was called at least once with the specified args
expect(mockFunc.mock.calls).toContain([arg1, arg2]);

// The last call to the mock function was called with the specified args
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
  arg1,
  arg2,
]);

// The first arg of the last call to the mock function was `42`
// (note that there is no sugar helper for this specific of an assertion)
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);

// A snapshot will check that a mock was invoked the same number of times,
// in the same order, with the same arguments. It will also assert on the name.
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.mock.getMockName()).toBe('a mock name');

For a complete list of matchers, check out the reference docs.

matchers 목록을 완벽하게 하기위해,  reference docs를 보자.


'Tool > jest' 카테고리의 다른 글

[번역] Jest - Testing Asynchronous Code  (0) 2018.11.09
[번역] Jest - Setup and Teardown  (0) 2018.11.08
[번역] Jest - Using Matchers  (0) 2018.11.07
[번역] Jest - An Async Example  (0) 2018.11.03
[번역] Jest - Testing React Native Apps  (0) 2018.11.01
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함