๐Ÿ“’

Mock ํ•จ์ˆ˜๋Š” ํ•จ์ˆ˜์˜ ์‹ค์ œ ๊ตฌํ˜„์„ ์ง€์šฐ๊ณ , ํ•จ์ˆ˜์— ๋Œ€ํ•œ ํ˜ธ์ถœ(๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ํ˜ธ์ถœ์— ์ „๋‹ฌ๋œ ๋งค๊ฐœ๋ณ€์ˆ˜)์„ ์บก์ฒ˜ํ•˜๊ณ , new์—ฐ์‚ฐ์ž๋กœ ์ธ์Šคํ„ด์Šคํ™”๋œ ์ƒ์„ฑ์ž ํ•จ์ˆ˜์˜ ์ธ์Šคํ„ด์Šฌ๋ฅด ์บก์ฒ˜ํ•˜๊ณ , ๋ฐ˜ํ™˜๊ฐ’์˜ test-time configuration์„ ํ—ˆ์šฉํ•˜์—ฌ ์ฝ”๋“œ ๊ฐ„ ์—ฐ๊ฒฐ์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

๋ชจ์˜ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

โœ”๏ธUsing a mock function

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 property

๋ชจ๋“  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');

โœ”๏ธ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

๋ชจ์˜ ํ•จ์ˆ˜๋Š” ์—ฐ์† ์ „๋‹ฌ ์Šคํƒ€์ผ(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

โœ”๏ธMocking Modules

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));
});

โœ”๏ธMocking Partials

๋ชจ๋“ˆ์˜ ์ผ๋ถ€๋Š” ๋ชจํ‚นํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ ์‹ค์ œ ๊ตฌํ˜„์„ ์œ ์ง€ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

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');
});

โœ”๏ธMocking implementations

๋ฐ˜ํ™˜ ๊ฐ’ ์ง€์ •์„ ๋„˜์–ด์„œ ๋ชจ์˜ ํ•จ์ˆ˜์˜ ๊ตฌํ˜„์„ ์™„์ „ํžˆ ๊ต์ฒดํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค

์ด๋Š” 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;
  }),
};

โœ”๏ธMock Names

์„ ํƒ์ ์œผ๋กœ ๋ชจ์˜ ํ•จ์ˆ˜์— ์ด๋ฆ„์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ์ด๋ฆ„์€ ํ…Œ์ŠคํŠธ ์˜ค๋ฅ˜ ์ถœ๋ ฅ์—์„œ 'jest.fn()' ๋Œ€์‹  ํ‘œ์‹œ๋œ๋‹ค.

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ณด๊ณ ํ•˜๋Š” ๋ชจ์˜ ํ•จ์ˆ˜๋ฅผ ๋น ๋ฅด๊ฒŒ ์‹๋ณ„ํ•˜๋ ค๋ฉด .mockName()์„ ์‚ฌ์šฉํ•˜์ž.

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

โœ”๏ธCustom Matchers

๋ชจ์˜ ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ป๊ฒŒ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ๋œ ๊นŒ๋‹ค๋กญ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด, ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ๋งž์ถคํ˜• ๋งค์ฒ˜ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค.

// 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');
profile
๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€

Powered by GraphCDN, the GraphQL CDN