Mock ํจ์๋ ํจ์์ ์ค์ ๊ตฌํ์ ์ง์ฐ๊ณ , ํจ์์ ๋ํ ํธ์ถ(๊ทธ๋ฆฌ๊ณ ํด๋น ํธ์ถ์ ์ ๋ฌ๋ ๋งค๊ฐ๋ณ์)์ ์บก์ฒํ๊ณ , new
์ฐ์ฐ์๋ก ์ธ์คํด์คํ๋ ์์ฑ์ ํจ์์ ์ธ์คํด์ฌ๋ฅด ์บก์ฒํ๊ณ , ๋ฐํ๊ฐ์ test-time configuration์ ํ์ฉํ์ฌ ์ฝ๋ ๊ฐ ์ฐ๊ฒฐ์ ํ
์คํธํ ์ ์๊ฒ ํด์ค๋ค.
๋ชจ์ ํจ์๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ ๊ฐ์ง๊ฐ ์๋ค.
forEach
ํจ์๋ฅผ ํ
์คํธ ํ๋ค๊ณ ๊ฐ์ ํ์. ๊ทธ๋ฆฌ๊ณ ์ด forEach
ํจ์๋ ๊ฐ ์์๋ง๋ค ์ฝ๋ฐฑํจ์๋ฅผ ํธ์ถํ๋ค.
export function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
์ด ํจ์๋ฅผ ํ ์คํธํ๊ธฐ ์ํด mock function์ ์ฌ์ฉํ๋ค. ๊ทธ๋ฆฌ๊ณ mock ์ํ๋ฅผ ๊ฒ์ฌํ์ฌ ์ฝ๋ฐฑ์ด ์์๋๋ก ํธ์ถ๋๋์ง ํ์ธํ ์ ์๋ค.
const forEach = require('./forEach');
const mockCallback = jest.fn(x => 42 + x);
test('forEach mock function', () => {
forEach([0, 1], mockCallback);
// The mock function was called twice
expect(mockCallback.mock.calls).toHaveLength(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);
});
๋ค๋ฅธ ์์
function exampleFunction(callback) {
// ์ฌ๊ธฐ์์ ์ฝ๋ฐฑ์ด ์์๋๋ก ํธ์ถ๋๋์ง ํ์ธ ํ์
callback();
}
import { exampleFunction } from './exampleFunction';
test('exampleFunction test', () => {
const mockCallback = jest.fn(); // ๋ชจ์ ํจ์ ์์ฑ
exampleFunction(mockCallback); // ํจ์ ํธ์ถ
// ์ฝ๋ฐฑ์ด ํ ๋ฒ ํธ์ถ๋์๋์ง ๊ฒ์ฌ
expect(mockCallback).toHaveBeenCalledTimes(1);
// ํ์ํ ๊ฒฝ์ฐ ๋ค๋ฅธ ๊ฒ์ฌ๋ฅผ ์ํํ ์ ์์
// ์: expect(mockCallback).toHaveBeenCalledWith(/* expectedParameters */);
});
๋ชจ๋ mock ํจ์๋ .mock
ํ๋กํผํฐ๋ฅผ ๊ฐ์ง๋ค. .mock
ํ๋กํผํฐ๋ ํจ์ ํธ์ถ ๋ฐฉ์๊ณผ ํจ์๊ฐ ๋ฐํํ๋ ๊ฐ์ ๋ํ ์ ๋ณด๊ฐ ์ ์ฅ๋๋ ๊ณณ์ด๋ค.
.mock
ํ๋กํผํฐ๋ ๊ฐ ํธ์ถ์ ๋ํด this
๊ฐ์ ํธ๋ํนํ๊ธฐ ๋๋ฌธ์, ์ด ๊ฐ์ ๊ฒ์ฌํ ์๋ ์๋ค.
const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]
const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]
์ด๋ฌํ mock member๋ค์ ํ ์คํธ์์ ์ด๋ฌํ ํจ์๋ค์ด ์ด๋ป๊ฒ ํธ์ถ๋๊ณ , ์ธ์คํด์คํ๋๋ฉฐ, ๋ฌด์์ ๋ฐํํ๋์ง ํ์ธํ๋๋ฐ ๋งค์ฐ ์ ์ฉํ๋ค.
// The function was called exactly once
expect(someMockFunction.mock.calls).toHaveLength(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');
// The function was called with a certain `this` context: the `element` object.
expect(someMockFunction.mock.contexts[0]).toBe(element);
// 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).toBe('test');
// The first argument of the last call to the function was 'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');
๋ชจ์ ํจ์๋ค์ ๋ํ ํ ์คํธ ์ค์ ์ฝ๋์ ํ ์คํธ ๊ฐ์ ์ฃผ์ ํ๋ ๋ฐ ์ฌ์ฉํ ์๋ ์๋ค.
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
๋ชจ์ ํจ์๋ ์ฐ์ ์ ๋ฌ ์คํ์ผ(continuation-passing style)๋ก ์์ฑ๋ ์ฝ๋์์๋ ๋งค์ฐ ํจ๊ณผ์ ์ด๋ค.
์ด๋ฌํ ์คํ์ผ๋ก ์์ฑ๋ ์ฝ๋๋ ์ค์ ๊ตฌ์ฑ ์์์ ๋์์ ์ฌํํ๋ ๋ณต์กํ ์คํ ์ ์ฌ์ฉํ ํ์ ์๊ฒ ํด์ค๋ค.
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
API๋ก๋ถํฐ user ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ํด๋์ค๊ฐ ์๋ค๊ณ ๊ฐ์ ํ์. ์ด ํด๋์ค๋ API ํธ์ถ์ ์ํด axios๋ฅผ ์ฌ์ฉํ๊ณ ๋ชจ๋ user๋ค์ ํฌํจํ๊ณ ์๋ data
attribute๋ฅผ ๋ฐํํ๋ค.
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
์ด ๋ฉ์๋๋ฅผ ์ค์ API ํธ์ถ ์์ด ํ
์คํธํ๊ธฐ ์ํด์ axios ๋ชจ๋์ ์๋์ผ๋ก ๋ชจํน ํด์ฃผ๋ jest.mock(...)
ํจ์๋ฅผ ์ธ ์ ์๋ค.
๋ชจ๋์ ๋ชจํนํ ํ, .get
์ ์ํ mockResolvedValue
๋ฅผ ์ ๊ณตํ์ฌ ํ
์คํธ๊ฐ ๊ฒ์ฆํ๋ ค๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋๋ก ์ค์ ํ ์ ์๋ค.
์ฆ, axios.get('/users.json')์ด ๊ฐ์ง ์๋ต์ ๋ฐํํ๋๋ก ํ๋ ๊ฒ์ด๋ค.
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));
});
๋ชจ๋์ ์ผ๋ถ๋ ๋ชจํนํ๊ณ ๋๋จธ์ง ๋ถ๋ถ์ ์ค์ ๊ตฌํ์ ์ ์งํ๋๋ก ํ ์ ์๋ค.
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');
});
๋ฐํ ๊ฐ ์ง์ ์ ๋์ด์ ๋ชจ์ ํจ์์ ๊ตฌํ์ ์์ ํ ๊ต์ฒดํ๋ ๊ฒ์ด ์ ์ฉํ ๊ฒฝ์ฐ๊ฐ ์๋ค
์ด๋ jest.fn ๋๋ mock function๋ค์ ์๋ mockImplementationOnce
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ํํ ์ ์๋ค.
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
// > true
mockImplementation ๋ฉ์๋๋ ๋ค๋ฅธ ๋ชจ๋๋ก๋ถํฐ ์์ฑ๋ mock function์ ๊ธฐ๋ณธ ๊ตฌํ์ ์ ์ํด์ผ ํ ๋ ์ ์ฉํฉ๋๋ค.
//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
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
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()
ํจ์์ ํํ๋ก ์ด๋ฅผ ๋จ์ํํ๋ API๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
const myObj = {
myMethod: jest.fn().mockReturnThis(),
};
// is the same as
const otherObj = {
myMethod: jest.fn(function () {
return this;
}),
};
์ ํ์ ์ผ๋ก ๋ชจ์ ํจ์์ ์ด๋ฆ์ ์ ๊ณตํ ์ ์์ผ๋ฉฐ, ์ด ์ด๋ฆ์ ํ
์คํธ ์ค๋ฅ ์ถ๋ ฅ์์ 'jest.fn()'
๋์ ํ์๋๋ค.
ํ
์คํธ ๊ฒฐ๊ณผ์์ ์ค๋ฅ๋ฅผ ๋ณด๊ณ ํ๋ ๋ชจ์ ํจ์๋ฅผ ๋น ๋ฅด๊ฒ ์๋ณํ๋ ค๋ฉด .mockName()
์ ์ฌ์ฉํ์.
const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockImplementation(scalar => 42 + scalar)
.mockName('add42');
๋ชจ์ ํจ์๊ฐ ์ด๋ป๊ฒ ํธ์ถ๋์๋์ง ํ์ธํ๋ ๊ฒ์ ๋ ๊น๋ค๋กญ๊ฒ ํ๊ธฐ ์ํด, ์ฌ์ฉ์๋ฅผ ์ํ ๋ช ๊ฐ์ง ๋ง์ถคํ ๋งค์ฒ ํจ์๊ฐ ์๋ค.
// 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();
์ด๋ฌํ matcher๋ค์ .mock
property๋ฅผ ๊ฒ์ฌํ๋ ์ผ๋ฐ์ ์ธ ๋ฐฉ์์ ๋ํ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด๋ค.
๋ ์ ํธํ๋ ๋ฐฉ์์ด๋ ๋ ๊ตฌ์ฒด์ ์ธ ์์ ์ด ํ์ํ ๊ฒฝ์ฐ์๋ ์ด ์์ ์ ์ง์ ์ํํ๊ฑฐ๋ ์๋์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
// 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).toContainEqual([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.getMockName()).toBe('a mock name');