Jest: ํŠน์ • ๋ชจ๋“ˆ ๊ธฐ๋Šฅ์„ ์กฐ๋กฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

์— ๋งŒ๋“  2016๋…„ 04์›” 25์ผ  ยท  116์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: facebook/jest

๋‚˜๋Š” ์‰ฝ๊ณ  ๋ถ„๋ช…ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ ์–ด๋–ค ์ด์œ ์—์„œ์ธ์ง€ ์•Œ ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๋กœ ์–ด๋ ค์›€์„ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์„ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค. ๋‹ค์Œ์€ myModule.js .

export function foo() {...}
export function bar() {...}
export function baz() {...}

ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ๋ชจ๋“ˆ์„ ์ž ๊ธˆ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.

jest.unmock('./myModule.js');

๊ทธ๋Ÿฌ๋‚˜ ๋‚ด ๋ฐฑ์—”๋“œ์— ๋Œ€ํ•œ ์•„์•ฝ์Šค ํ˜ธ์ถœ์„ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์— foo ์กฐ๋กฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์˜ ๋ชจ๋“  ํ•จ์ˆ˜๊ฐ€ ์กฐ๋กฑ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ์œ ์ง€๋˜๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. foo ๋ฅผ ์˜ˆ์ƒํ•˜๊ณ  ์กฐ๋กฑ์„ ๋ฐ›๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  bar ๋ฐ baz ํ•จ์ˆ˜๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ foo ํ˜ธ์ถœํ•˜๋ฏ€๋กœ ๋‚ด ํ…Œ์ŠคํŠธ๊ฐ€ bar() ํ˜ธ์ถœํ•˜๋ฉด ์กฐ๋กฑ๋˜์ง€ ์•Š์€ bar ์กฐ๋กฑ foo .

unmock ๋ฐ mock ํ˜ธ์ถœ์ด ์ „์ฒด ๋ชจ๋“ˆ์—์„œ ์ž‘๋™ํ•˜๋Š” jest ๋ฌธ์„œ์— ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ํŠน์ • ๊ธฐ๋Šฅ์„ ์–ด๋–ป๊ฒŒ ์กฐ๋กฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์ ์ ˆํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‚ด ์ฝ”๋“œ๋ฅผ ๋ณ„๋„์˜ ๋ชจ๋“ˆ๋กœ ์ž„์˜๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์€ ์–ด๋ฆฌ์„์€ ์ผ์ž…๋‹ˆ๋‹ค.

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

๋„Œ ํ•  ์ˆ˜์žˆ์–ด:

jest.unmock('./myModule.js');

const myModule = require('myModule');
myModule.foo = jest.fn();

http://facebook.github.io/jest/docs/api.html#mock -functions ์ฐธ์กฐ

๋ชจ๋“  116 ๋Œ“๊ธ€

์‹ฌ์ธต ๋ถ„์„์—์„œ jest-mock์€ ์ „์ฒด ๋ชจ๋“ˆ์— ๋Œ€ํ•œ AST๋ฅผ ์ƒ์„ฑํ•œ ๋‹ค์Œ ํ•ด๋‹น AST๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›๋ณธ์˜ ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ์ค€์ˆ˜ํ•˜๋Š” ๋ชจ์˜ ๋ชจ๋“ˆ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. https://github.com/facebook/jest/tree/master/packages / ๋†๋‹ด ์กฐ๋กฑ

Python์˜ ๋ชจ์˜(https://docs.python.org/3/library/unittest.mock-examples.html)์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ๊ธฐ๋Šฅ์„ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๊ธฐ๋ณธ์ ์ธ ํ…Œ์ŠคํŠธ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.

๋ชจ๋“ˆ์˜ ์ผ๋ถ€๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ ๊ทน ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. jest-mock์€ ์กฐ๊ฑด๋ถ€๋กœ mocking์—์„œ ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์›๋ž˜ ๊ตฌํ˜„์„ ์ฐธ์กฐํ•˜๋„๋ก ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋„Œ ํ•  ์ˆ˜์žˆ์–ด:

jest.unmock('./myModule.js');

const myModule = require('myModule');
myModule.foo = jest.fn();

http://facebook.github.io/jest/docs/api.html#mock -functions ์ฐธ์กฐ

require ์ž‘๋™ ๋ฐฉ์‹์— ๋Œ€ํ•œ ๊ทผ๋ณธ์ ์ธ ์˜คํ•ด๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. require() ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ชจ๋“ˆ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์–ป์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ์˜ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ฐธ์กฐ๊ฐ€ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๋ชจ๋“ˆ์˜ ๊ฐ’์„ ๋ฎ์–ด์“ฐ๋ฉด ์ž์‹ ์˜ ์ฐธ์กฐ๋ฅผ ๋ฎ์–ด์“ฐ์ง€๋งŒ _๊ตฌํ˜„์€ ์›๋ž˜ ์ฐธ์กฐ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค_.

๊ท€ํ•˜์˜ ์˜ˆ์—์„œ myModule.foo() ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์˜ˆ, ๋ชจ์˜ ๋ฒ„์ „์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‚ด๋ถ€์ ์œผ๋กœ foo() ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” myModule.bar() ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ฐธ์กฐํ•˜๋Š” foo _๋ฎ์–ด์“ด ๋ฒ„์ „์ด ์•„๋‹™๋‹ˆ๋‹ค_. ๋‹น์‹ ์ด ๋‚˜๋ฅผ ๋ฏฟ์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋‹น์‹ ์€ ๊ทธ๊ฒƒ์„ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๊ท€ํ•˜๊ฐ€ ์„ค๋ช…ํ•œ ์˜ˆ๋Š” ๋‚ด๊ฐ€ ๊ฐ€์ง„ ๋ฌธ์ œ์— ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ๋ชจ๋ฅด๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

@cpojer

๋‚˜๋Š” ์ด๊ฒƒ์„ ์•„์ฃผ ์ž˜ ์ดํ•ดํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ๋ฏฟ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ babel์ด ๋ชจ๋“ˆ์„ ์ปดํŒŒ์ผํ•˜๋Š” ๋ฐฉ์‹์€ ์ด๊ฒƒ์„ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์ง€ ์•Š์œผ๋ฉฐ ๋‚˜๋Š” ๋‹น์‹ ์˜ ํ˜ผ๋ž€์„ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ์ด๊ฒƒ์ด ๋ชจ๋“ˆ์ด ์žˆ๋Š” ์‹ค์ œ ES2015 ํ™˜๊ฒฝ์—์„œ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ• ์ง€ ์ •ํ™•ํžˆ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ์ฃผ๋กœ ๊ทธ๋Ÿฌํ•œ ํ™˜๊ฒฝ์ด ํ˜„์žฌ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(์•„์ง ์‹œ๋„ํ•˜์ง€ ์•Š์€ ์ตœ์‹  ๋ฒ„์ „์˜ Chrome Canary ์ œ์™ธ). ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์„ค๋ช…ํ•˜๋ ค๋ฉด babel ์ฝ”๋“œ์˜ ์ปดํŒŒ์ผ๋œ ์ถœ๋ ฅ์„ ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

var foo = function foo() {};
var bar = function bar() { foo(); };

exports.foo = foo;
exports.bar = bar;

์ด ๊ฒฝ์šฐ foo๋ฅผ ์กฐ๋กฑํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด ์‚ฌ์‹ค์ด๋ฉฐ ์ดˆ๊ธฐ ๋ฌธ์ œ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฝ์ง€ ๋ชปํ•œ ์ ์— ๋Œ€ํ•ด ์‚ฌ๊ณผ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ foo ๊ฐ€ ํ˜ธ์ถœ๋œ ๋ฐฉ์‹์— ๋Œ€ํ•œ ๊ฐ€์ •์„ ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— exports.foo() ๋ผ๊ณ  ๊ฐ€์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•˜๋ฉด:

var foo = function foo() {};
var bar = function bar() { exports.foo(); };

exports.foo = foo;
exports.bar = bar;

๊ทธ๋Ÿฐ ๋‹ค์Œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์—์„œ ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

var module = require('../module');
module.foo = jest.fn();
module.bar();

์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์šฐ๋ฆฌ๊ฐ€ ES2015๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” Facebook์—์„œ ํ•˜๋Š” ์ผ์ž…๋‹ˆ๋‹ค.

ES2015 ๋ชจ๋“ˆ์€ ๋‚ด๋ณด๋‚ด๋Š” ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ๋ฐ”์ธ๋”ฉ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ ํ˜„์žฌ babel์ด ์ปดํŒŒ์ผํ•˜๋Š” ๊ธฐ๋ณธ ์ปดํŒŒ์ผ๋œ ์ฝ”๋“œ๋Š” ์ด๋Ÿฌํ•œ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›๋˜๋Š” ๋ชจ๋“ˆ์ด ์žˆ๋Š” ์—„๊ฒฉํ•œ ES2015 ๋ชจ๋“ˆ ํ™˜๊ฒฝ์—์„œ ๊ท€ํ•˜๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๊ฒƒ์„ ์ •ํ™•ํžˆ ์ง€์›ํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. jest-mock ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์€ ๋ชจ๋“ˆ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์‹คํ–‰ํ•œ ๋‹ค์Œ ๋ชจ๋“ˆ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋ชจ์˜ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ, ์ด ๊ฒฝ์šฐ foo ์˜ ๋กœ์ปฌ ๋ฐ”์ธ๋”ฉ์„ ์ˆ˜์ •ํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์•„์ด๋””์–ด๊ฐ€ ์žˆ์œผ๋ฉด ์—ฌ๊ธฐ ๋˜๋Š” ํ’€ ์š”์ฒญ์œผ๋กœ ๊ธฐ์—ฌํ•˜์‹ญ์‹œ์˜ค. https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct ์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ด ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ํ–‰๋™ ๊ฐ•๋ น์ด ์žˆ์Œ์„ ์ƒ๊ธฐ์‹œํ‚ค๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

์˜ˆ์ œ์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์†”๋ฃจ์…˜์€ foo๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ foo๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ์ƒ์œ„ ์ˆ˜์ค€ API(์˜ˆ: XMLHttpRequest ๋˜๋Š” ๋Œ€์‹  ์‚ฌ์šฉํ•˜๋Š” ์ถ”์ƒํ™”)๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@cpojer ์ž์„ธํ•œ ์„ค๋ช… ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋‚ด ์–ธ์–ด๋กœ ๊ธฐ๋ถ„์„ ์ƒํ•˜๊ฒŒ ํ–ˆ๋‹ค๋ฉด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ์—”์ง€๋‹ˆ์–ด๋ง ์ž‘์„ฑ์— ๋งค์šฐ ๋Šฅ์ˆ™ํ•˜๋ฉฐ ์ตœ๋Œ€ํ•œ ๋นจ๋ฆฌ ์ œ ์š”์ ์„ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ์ƒํ™ฉ์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด 5์‹œ๊ฐ„ ๋™์•ˆ ์ด ๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ–ˆ๊ณ  2๊ฐœ์˜ ์ž์„ธํ•œ ์„ค๋ช…์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ท€ํ•˜๋Š” ๋‚ด ๋‘ ์ง„์ˆ ์˜ ์š”์ ์„ ์™„์ „ํžˆ ๋†“์นœ ์งง์€ ๋ฉ”์‹œ์ง€๋กœ ๋งˆ๋ฌด๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด ๋ฐ”๋กœ ๋‹ค์Œ ๋ฉ”์‹œ์ง€๊ฐ€ 1) ๋‚ด๊ฐ€ ๋งํ•˜๊ณ  ์žˆ๋Š” ์š”์ ์„ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ๊ฑฐ๋‚˜ 2) require() ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— "๊ทผ๋ณธ์ ์ธ ์˜คํ•ด"๊ฐ€ ์žˆ๋‹ค๊ณ  ๋งํ•œ ์ด์œ ์ž…๋‹ˆ๋‹ค. ๊ณ ๋ง™๊ฒŒ๋„ ์˜ต์…˜ 1์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋‚ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ๊ฐ€๋Šฅํ•œ ํ•ด๊ฒฐ์ฑ…์„ ์ˆ™๊ณ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ง€๊ธˆ์€ ๋‚ฎ์€ ์ˆ˜์ค€์˜ API๋ฅผ ์กฐ๋กฑํ–ˆ์ง€๋งŒ, ํ™•์‹คํžˆ ์œ ์šฉํ•  ๊ฒƒ์ด๋ฏ€๋กœ ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ์กฐ๋กฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ๊ฒƒ์ด๋ผ๋Š” ๋ฐ ๋™์˜ํ•˜์ง€๋งŒ JS์—์„œ (์•„๋งˆ๋„ ๋Š๋ฆฐ) ์ •์  ๋ถ„์„์„ ์‚ฌ์ „์— ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  ์ˆ˜ํ–‰ํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์—†์Šต๋‹ˆ๋‹ค.

@cpojer : 5๊ฐœ์›” ํ›„์— ์—ฌ๊ธฐ๋กœ ๋›ฐ์–ด๋“œ๋Š” ๊ฒƒ์ด ๊ฐˆ ๊ธธ์ธ์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†์ง€๋งŒ ์ด์— ๋Œ€ํ•œ ๋‹ค๋ฅธ ๋Œ€ํ™”๋Š” ์ฐพ์„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

์œ„์˜ ์ œ์•ˆ์—์„œ ๋ฒ—์–ด๋‚˜ ๋™์ผํ•œ ๋ชจ๋“ˆ์˜ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์—์„œ ํ•œ ๊ธฐ๋Šฅ์„ ์กฐ๋กฑํ•˜๊ธฐ ์œ„ํ•ด ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

jest.unmock('./someModule.js');
import someModule from './someModule.js';

it('function1 calls function 2', () => {
    someModule.function2 = jest.fn();

    someModule.function1(...);

    expect(someModule.function2).toHaveBeenCalledWith(...);
});

์ด๊ฒƒ์€ ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ์—์„œ ์ž‘๋™ํ•˜์ง€๋งŒ ํ•˜๋‚˜์˜ it(...); ๋ธ”๋ก์œผ๋กœ ๋ถ„๋ฆฌ๋œ ๋ฐฉ์‹์œผ๋กœ ์ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ชจ๋“  ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋ฏ€๋กœ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์—์„œ ์‹ค์ œ function2 ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ํŒ์ด ์žˆ๋‚˜์š”?

๋‹น์‹ ์€ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค .mockClear ์—์„œ ํ•จ์ˆ˜์— beforeEach ๋˜๋Š” ์ „ํ™” jest.clearAllMocks() ๋‹น์‹ ์ด ๋†๋‹ด (16) ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ.

์•ˆ๋…•ํ•˜์„ธ์š” @cpojer์ž…๋‹ˆ๋‹ค! ์ €๋Š” Jest 16์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. jest.clearAllMocks() ๋˜๋Š” someModule.function2.mockClear() ๋‘˜ ๋‹ค ์ €์—๊ฒŒ ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋“ค์€ ๋ชจ์˜๊ฐ€ ๊ฐ€์ ธ์˜จ ๋ชจ๋“ˆ์˜ ๊ธฐ๋Šฅ์ด ์•„๋‹Œ ์ „์ฒด ๋ชจ๋“ˆ์ผ ๋•Œ๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ํ•จ์ˆ˜๋Š” ํ›„์† ํ…Œ์ŠคํŠธ์—์„œ ๋ชจ์˜ โ€‹โ€‹์ƒํƒœ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์˜ˆ์ƒ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ž‘์€ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ์—์„œ ๋ณต์ œํ•˜๊ณ  ์ƒˆ ๋ฌธ์ œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ข‹์€ ์ƒ๊ฐ?

@cpojer -

์˜ˆ์ œ์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์†”๋ฃจ์…˜์€ foo๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ foo๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ์ƒ์œ„ ์ˆ˜์ค€ API(์˜ˆ: XMLHttpRequest ๋˜๋Š” ๋Œ€์‹  ์‚ฌ์šฉํ•˜๋Š” ์ถ”์ƒํ™”)๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ €๋Š” Jest๋ฅผ ์ฒ˜์Œ ์‚ฌ์šฉํ•˜๊ณ  ๋น„์Šทํ•œ ๋ฌธ์ œ๋กœ ์–ด๋ ค์›€์„ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ํ›„๋“œ ์•„๋ž˜์—์„œ XMLHttpRequest ์‚ฌ์šฉํ•˜๋Š” axios ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ axios ๋ฅผ ์กฐ๋กฑํ•˜๊ณ  ์‹ถ์ง€ ์•Š์ง€๋งŒ ์‹ค์ œ XMLHttpRequest ๋ฅผ ์กฐ๋กฑํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์ž์‹ ์— ์˜ํ•ด ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค ์ด . ์ด๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๊นŒ?

๊ฐ์‚ฌ ํ•ด์š”!

๋„ค, ์ด์™€ ๊ฐ™์€ ๊ฒƒ์ด ๋‹น์‹ ์„ ์˜ฌ๋ฐ”๋ฅธ ๊ธธ๋กœ ์ธ๋„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค! :) jest.fn ๋ฅผ ๋” ๋ฉ‹์ง„ API๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”. :D

์—ฌ๊ธฐ์—์„œ ๊ท€ํ•˜์˜ ์˜๊ฒฌ์— ๊ด€ํ•œ @cpojer : https://github.com/facebook/jest/issues/936#issuecomment -214939935

ES2015๋กœ ์–ด๋–ป๊ฒŒ ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

// myModyle.js
export foo = (string) => "foo-" + string
export bar = (string2) => foo(string2)

// myModule.test.js
var module = require('./myModule');

// how do I mock foo here so this test passes?
expect(bar("hello")).toEqual("test-hello")

์†”๋ฃจ์…˜์„ ์ฐพ๊ณ  ์žˆ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ์ ‘ํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ ๋งŽ์€ const/function์„ ๋‚ด๋ณด๋‚ด๊ณ  ํ…Œ์ŠคํŠธ ์ค‘์ธ ํŒŒ์ผ๋กœ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๋‹ค์Œ์ด ์ €์—๊ฒŒ ๋„์›€์ด ๋˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

`` javascript function mockFunctions() { const original = require.requireActual('../myModule'); return { ...original, //Pass down all the exported objects test: jest.fn(() => {console.log('I didnt call the original')}), someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}), } jest.mock('../myModule', () => mockFunctions()); const storage = require.requireMock('../myModule');

@ainesophaur , ๋‚ด๊ฐ€ ์—ฌ๊ธฐ์„œ ๋ญ˜ ์ž˜๋ชปํ•˜๊ณ  ์žˆ๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค
์ €๋Š” ํ˜„์žฌ jest 18.1(๋ฐ create-react-app 0.9.4)์„ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค.

...<codes from comment above>..

// Let's say the original myModule has a function doSmth() that calls test()
storage.doSmth();
expect(storage.test).toHaveBeenCalled();

ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ๊ณผ ํ•จ๊ป˜ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.

@huyph jest ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด doSmth ๋ฉ”์„œ๋“œ์™€ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์กฐ๋กฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์กฐ๋กฑ ์ฝ”๋“œ์˜ ์Šค๋‹ˆํŽซ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ฌด์—‡์ด ์ž˜๋ชป๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@ainesophaur ... test() ๋ฉ”์„œ๋“œ๋ฅผ ์กฐ๋กฑํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๊นŒ? ์ด ๋ถ€๋ถ„: test: jest.fn(() => {console.log('I didnt call the original')}),

@ainesophaur ๊ท€ํ•˜์˜ ์ฝ”๋“œ๋„ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋ชจ์˜ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ธฐ๋Œ€๋Š” ๊ฒฐ์ฝ” ์ถฉ์กฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” ์ด๊ฒƒ์ด ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๊ฒƒ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์š”๊ตฌํ•˜๋Š” ๋ฐฉ์‹์— ๋‚ด์žฌ๋˜์–ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์—ˆ์œผ๋ฉด ํ•ฉ๋‹ˆ๋‹ค.

@cpojer ๋ถ€๋ถ„์ ์œผ๋กœ ๋ชจ์˜ ๋ชจ๋“ˆ๊ณผ ๊ด€๋ จํ•˜์—ฌ ์ƒˆ๋กœ์šด ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

@rantonmattei & @huyph ๋ชจ์˜ ์ •์˜ ์Šค๋‹ˆํŽซ๊ณผ ์‹คํ–‰ ์ค‘์ธ ํ…Œ์ŠคํŠธ๋ฅผ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๊ตฌํ˜„ ํŒŒ์ผ์ด ํ•„์š”/๊ฐ€์ ธ์˜ค๊ธฐ ์ „์— ๋ชจ์˜๋ฅผ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. JEST๋กœ ์ž‘์—…ํ•œ ์ง€ ๊ฝค ๋˜์—ˆ์ง€๋งŒ, node_modules ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“  ๋‚ด ์•ฑ์˜ ํŒŒ์ผ์ด๋“  ๊ฐ„์— ๋‚ด๊ฐ€ ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฒƒ์„ ๋ชจ์˜ํ•˜๊ธฐ ์œ„ํ•ด ๊ฒฐ๊ตญ JEST๋ฅผ ์–ป์—ˆ์Œ์„ ๊ธฐ์–ตํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ATM ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ๋ถ€์กฑํ•˜์ง€๋งŒ Jest๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…ํ•œ ํ”„๋กœ์ ํŠธ์˜ ๋ช‡ ๊ฐ€์ง€ ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ข…์†์„ฑ์—์„œ ํŒŒ์ผ ์กฐ๋กฑ

์ด ์˜ˆ์ œ์—์„œ ์‹ค์ œ ํ•จ์ˆ˜ ์ •์˜๋Š” react-native์— ์˜ํ•ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. "react-native/Libraries/Utilities/dismissKeyboard.js" ํŒŒ์ผ์„ ์กฐ๋กฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

__mocks__/react-native/Libraries/Utilities/dismissKeyboard.js ์•„๋ž˜์˜ ๋ชจ์˜ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.

function storeMockFunctions() {
  return jest.fn().mockImplementation(() => true);
}
jest.mock('react-native/Libraries/Utilities/dismissKeyboard', () => storeMockFunctions(), { virtual: true });
const dismissKeyboard = require('react-native/Libraries/Utilities/dismissKeyboard');
exports = module.exports = storeMockFunctions;

์œ„์—์„œ ์‚ฌ์šฉํ•œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์ง€๋งŒ ๋ชจ๋“ˆ์„ ์š”๊ตฌํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค. jest๊ฐ€ __mocks__์—์„œ ์ฐพ์€ ๋‹ค์Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

expect(dismissKeyboard.mock.calls).toHaveLength(1);

์ œ์–ดํ•˜๋Š” ํŒŒ์ผ ์กฐ๋กฑ
์‹ค์ œ ํ•จ์ˆ˜ ์ •์˜

export const setMerchantStores = (stores) => storage.set('stores', stores);

๋ชจ์˜ ํ…Œ์ŠคํŠธ ํŒŒ์ผ

const { storeListEpic, offerListEpic } = require('../merchant');

function storeMockFunctions() {
  const original = require.requireActual('../../common/Storage');
  return {
    ...original,
    setMerchantStores: jest.fn((...args) => original.setMerchantStores(...args)),
    setMerchantOffers: jest.fn((...args) => original.setMerchantOffers(...args)),
  };
}
jest.mock('../../common/Storage', () => storeMockFunctions());
import * as storage from '../../common/Storage';

afterEach(() => {
  storage.setMerchantStores.mockClear();
});

it('handle storeListEpic type STORE_LIST_REQUEST -> STORE_LIST_SUCCESS', async () => {
  const scope = nock('http://url')
  .get('/api/merchant/me/stores')
  .reply(200, storeData);
  const result = await storeListEpic(ActionsObservable.of(listStores())).toPromise();
  expect(storage.setMerchantStores.mock.calls).toHaveLength(1);
  expect(await storage.getMerchantStores()).toEqual({ ids: storesApiData.result, list: storesApiData.entities.store});
});

@ainesophaur๋ฅผ ๊ณต์œ ํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ์—ฌ์ „ํžˆ jest 18.1์—์„œ ์ž‘๋™ํ•˜๋„๋ก ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚ด ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

it('should save session correctly', () => {

  function mockFunctions() {
    const original = require.requireActual('./session');
    return {
      ...original,
      restartCheckExpiryDateTimeout: jest.fn((() => {
        console.log('I didn\'t call the original');
      })),
    }
  }

  jest.mock('./session', () => mockFunctions());
  const mockSession = require('./session');

  // NOTE: saveSession() will call the original restartCheckExpiryDateTimeout() instead of my
  // mock one. However, mockSession.restartCheckExpiryDateTimeout() does call the mock one
  mockSession.saveSession('', getTomorrowDate(), 'AUTH');

  // mockSession.restartCheckExpiryDateTimeout(); // does print out "I didn't call the original"

  expect(mockSession.restartCheckExpiryDateTimeout).toHaveBeenCalled();
});

session.js

export function saveSession(sessionId, sessionExpiryDate, authToken) {
  ....
  restartCheckExpiryDateTimeout(sessionExpiryDate);
  ...
}
....

export function restartCheckExpiryDateTimeout(...) {
....
}

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๊ฑฐ ๋‹ค์‹œ ์—ด์–ด๋„ ๋ ๊นŒ์š”? @cpojer

@huyph saveSession ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ์‹์€ ๋ชจ๋“ˆ์„ ํ†ตํ•ด module.restartCheckExpiryDateTimeout ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  ๋กœ์ปฌ๋กœ ์ •์˜๋œ restartCheckExpiryDateTimeout ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ์กฐ๋กฑ๋œ module.restartCheckExpiryDateTimeout saveSession ๋Š” ์‹ค์ œ ์ •์˜๋œ restartCheckExpiryDateTimeout ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ module.restartCheckExpiryDateTimeout ๋Š” saveSession ์˜ํ•ด ๊ฐ์ง€๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

saveSession ๋ฅผ const ์— ํ• ๋‹นํ•œ ๋‹ค์Œ saveSession.restartCheckExpiryDateTimeout = () => {...logic} ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ๋•Œ๋Š” ๋‚ด์—์„œ saveSession.saveSession ์ „ํ™” saveSession.restartCheckExpiryDateTimeout ๋Œ€์‹ ์˜ restartCheckExpiryDateTimeout . ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•˜๋Š” ์‹ค์ œ ํ•จ์ˆ˜ saveSession ๋Œ€์‹  ์ƒˆ const๋ฅผ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ someVar.saveSession() ํ˜ธ์ถœํ•˜๋ฉด ๋‚ด๋ถ€์ ์œผ๋กœ saveSession.restartCheckExpiryDateTimeout() ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉฐ ํ˜„์žฌ ์กฐ๋กฑ๋ฉ๋‹ˆ๋‹ค.

restartCheckExpiryDateTimeout() ๊ฐ€ ๋‚ด๋ณด๋‚ธ ํ•จ์ˆ˜๋ผ๊ณ  ์ถ”๊ฐ€ํ–ˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. saveSession() ๋‚ด์—์„œ ๋กœ์ปฌ๋กœ ์ •์˜๋œ ํ•จ์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค ... (์œ„์˜ ๋‚ด ์˜๊ฒฌ์„ ์—…๋ฐ์ดํŠธํ•จ). ์ด ๊ฒฝ์šฐ module.saveSession() ๋Š” ์กฐ๋กฑ๋˜๋Š” ์˜ฌ๋ฐ”๋ฅธ module.restartCheckExpiryDateTimeout() ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋‚˜๋Š” ๋‹น์‹ ์ด ์œ„์—์„œ ์ œ์•ˆํ•œ ๊ฒƒ์„ ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. saveSession() ๋ฐ restartCheckExpiryDateTimeout() ๋ฅผ ๋ชจ๋‘ ๋‹ค๋ฅธ const๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์‚ฌ ํ•ด์š”

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด saveSession์˜ ๋ฒ”์œ„์— ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค. ์„ธ์ด๋ธŒ์„ธ์…˜์€
๋ถ€๋ชจ ๋ฒ”์œ„์—์„œ ํ˜•์ œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์— ์—ฌ๋Ÿฌ ๋ฒˆ ๋ถ€๋”ช์ณค๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๋‚ด๊ฐ€ ์ œ์•ˆํ•œ ๊ฒƒ์€ ํšจ๊ณผ๊ฐ€ ์žˆ์—ˆ๋‹ค

2017๋…„ 5์›” 8์ผ ์˜คํ›„ 8์‹œ 38๋ถ„์— "Huy Pham" [email protected]์ด ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” restartCheckExpiryDateTimeout()์ด ๋‚ด๋ณด๋‚ด์กŒ๋‹ค๊ณ  ์ถ”๊ฐ€ํ–ˆ์–ด์•ผ ํ–ˆ๋‹ค.
๊ธฐ๋Šฅ. saveSession() ๋‚ด์—์„œ ๋กœ์ปฌ๋กœ ์ •์˜๋œ ํ•จ์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค...

๋‚˜๋Š” ๋‹น์‹ ์ด ์œ„์—์„œ ์ œ์•ˆํ•œ ๊ฒƒ์„ ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐ์‚ฌ ํ•ด์š”

โ€”
๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธ
https://github.com/facebook/jest/issues/936#issuecomment-300029003 ๋˜๋Š” ์Œ์†Œ๊ฑฐ
์Šค๋ ˆ๋“œ
https://github.com/notifications/unsubscribe-auth/AEeBdsmpOOmzvcUHB3D_-Z7MChIzt10Pks5r37WYgaJpZM4IPGAH
.

๋ฐฉ๊ธˆ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: (์ฆ‰, ์›๋ž˜์˜ restartCheckExpiryDateTimeout()์ด ์—ฌ์ „ํžˆ ํ˜ธ์ถœ๋จ)

export session = {
   saveSession: () => {
      session.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

์ด๊ฒƒ์€ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค(์ฆ‰, ๋ชจ์˜ restartCheckExpiryDateTimeout()๊ฐ€ ๋Œ€์‹  ํ˜ธ์ถœ๋จ). ์ฐจ์ด๋Š” ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค function() ๋Œ€์‹  ํ™”์‚ดํ‘œ ํ˜•ํƒœ ๋ฐ ์‚ฌ์šฉ this. ๋Œ€์‹  session.

export session = {
   saveSession: function() {
      this.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

์ด ์ฝ”๋“œ๋ฅผ ๋†๋‹ด์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ....

pojo ๋Œ€์‹  ํด๋ž˜์Šค ๊ฐœ์ฒด๋กœ ๋‚ด๋ณด๋‚ด๋ณด์‹ญ์‹œ์˜ค. ๋‚˜๋Š” ๋ฏฟ๋Š”๋‹ค
ํŠธ๋žœ์ŠคํŒŒ์ผ๋Ÿฌ๋Š” ๋ณ€์ˆ˜๋ฅผ ๋‹ค๋ฅด๊ฒŒ ํ˜ธ์ด์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ž‘์—…์— ๋„์ฐฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค
ํ…Œ์ŠคํŠธ, ์•ฝ์†ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•œ์ง€ โ€‹โ€‹๋ฐ˜๋…„ ์ •๋„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
๋†๋‹ด์„ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ์ด ๋ฌธ์ œ๋ฅผ ์ž˜ ๊ธฐ์–ตํ•˜๊ณ  ๊ฒฐ๊ตญ ๊ธฐ์–ตํ•ฉ๋‹ˆ๋‹ค.
ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ๋Š” ๊ฒƒ.

2017๋…„ 5์›” 9์ผ ์˜ค์ „ 12์‹œ 53๋ถ„์— "Huy Pham" [email protected]์ด ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฐฉ๊ธˆ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: (์ฆ‰, ์›๋ž˜์˜ restartCheckExpiryDateTimeout()์€
์—ฌ์ „ํžˆ ํ˜ธ์ถœ๋จ)

๋‚ด๋ณด๋‚ด๊ธฐ ์„ธ์…˜ = {
์„ธ์ด๋ธŒ์„ธ์…˜: () => {
session.restartCheckExpiryDateTimeout();
},
restartCheckExpiryDateTimeout: () => {},
}

์ด๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: (์ฆ‰, ์›๋ž˜์˜ restartCheckExpiryDateTimeout()์€
์—ฌ์ „ํžˆ ํ˜ธ์ถœ๋จ)

๋‚ด๋ณด๋‚ด๊ธฐ ์„ธ์…˜ = {
์„ธ์ด๋ธŒ์„ธ์…˜: ํ•จ์ˆ˜() {
this.restartCheckExpiryDateTimeout();
},
restartCheckExpiryDateTimeout: () => {},
}

์ด ์ฝ”๋“œ๋ฅผ ๋†๋‹ด์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ....

โ€”
๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธ
https://github.com/facebook/jest/issues/936#issuecomment-300060975 ๋˜๋Š” ์Œ์†Œ๊ฑฐ
์Šค๋ ˆ๋“œ
https://github.com/notifications/unsubscribe-auth/AEeBdrRQExycPYiGtvm7qYi5G87w6b6Oks5r3_FlgaJpZM4IPGAH
.

@sorahn ๊ฐ™์€ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. es6 + babel , ์กฐ๋กฑํ•˜๋Š” ๋ฐฉ๋ฒ•?
@cpojer es6 + babel , export const function xx() {} , export many function , Jest ํ˜ธ์ถœ๋œ ๋ชจ๋“ˆ(ํŒŒ์ผ)์—์„œ ํ•จ์ˆ˜๋ฅผ ์กฐ๋กฑํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ๋ชจ๋“ˆ(ํŒŒ์ผ)์˜ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์œผ๋กœ? ํ…Œ์ŠคํŠธํ•ด๋ณด๋‹ˆ ๋งž๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. commonjs ํŒจํ„ด์— ๋Œ€ํ•ด์„œ๋งŒ Jest๋Š” ๊ท€ํ•˜์˜ ์˜ˆ์™€ ๊ฐ™์ด ํ•จ์ˆ˜๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@ainesophaur ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ธฐ์ค€ ์น˜์ˆ˜:

export const getMessage = (num: number): string => {
  return `Her name is ${genName(num)}`;
};

export function genName(num: number): string {
  return 'novaline';
}

์‹œํ—˜:

function mockFunctions() {
  const original = require.requireActual('../moduleA');
  return {
    ...original,
    genName: jest.fn(() => 'emilie')
  }
}
jest.mock('../moduleA', () => mockFunctions());
const moduleA = require('../moduleA');

describe('mock function', () => {

  it('t-0', () => {
    expect(jest.isMockFunction(moduleA.genName)).toBeTruthy();
  })

  it('t-1', () => {

    expect(moduleA.genName(1)).toBe('emilie');
    expect(moduleA.genName).toHaveBeenCalled();
    expect(moduleA.genName.mock.calls.length).toBe(1);
    expect(moduleA.getMessage(1)).toBe('Her name is emilie');
    expect(moduleA.genName.mock.calls.length).toBe(2);

  });

});

๊ฒ€์‚ฌ ๊ฒฐ๊ณผ:

FAIL  jest-examples/__test__/mock-function-0.spec.ts
  โ— mock function โ€บ t-1

    expect(received).toBe(expected)

    Expected value to be (using ===):
      "Her name is emilie"
    Received:
      "Her name is novaline"

      at Object.it (jest-examples/__test__/mock-function-0.spec.ts:22:35)
      at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)

  mock function
    โœ“ t-0 (1ms)
    โœ• t-1 (22ms)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        0.215s, estimated 1s

์œ„์˜ ๋งˆ์ง€๋ง‰ ๋ช‡ ๊ฐ€์ง€ ์˜๊ฒฌ์„ ๋ณด์‹ญ์‹œ์˜ค. ํŠนํžˆ ๋งˆ์ง€๋ง‰. ๋‹น์‹ ์˜
๋‚ด๋ณด๋‚ธ ๋ฉ”์„œ๋“œ๋Š” ๋กœ์ปฌ ๋ฒ”์œ„์˜ ํ˜•์ œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ 
์‹ค์ œ ๋‚ด๋ณด๋‚ธ ๋ฉ”์„œ๋“œ(๋ชจ์˜ ๊ฐ์ฒด๊ฐ€ ์žˆ๋Š” ๊ณณ)

2017๋…„ 5์›” 31์ผ ์˜ค์ „ 2์‹œ "novaline" [email protected]์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ผ์Šต๋‹ˆ๋‹ค.

@ainesophaur https://github.com/ainesophaur ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ธฐ์ค€ ์น˜์ˆ˜:

๋‚ด๋ณด๋‚ด๊ธฐ const getMessage = (์ˆซ์ž: ์ˆซ์ž): ๋ฌธ์ž์—ด => {
๋ฐ˜ํ™˜ Her name is ${genName(num)} ;
};
๋‚ด๋ณด๋‚ด๊ธฐ ํ•จ์ˆ˜ genName(num: number): string {
๋ฐ˜ํ™˜ '๋…ธ๋ฐ”๋ฆฐ';
}

์‹œํ—˜:

ํ•จ์ˆ˜ mockFunctions() {
const ์›๋ณธ = require.requireActual('../moduleA');
๋ฐ˜ํ’ˆ {
...์›๋ž˜์˜,
genName: jest.fn(() => '์—๋ฐ€๋ฆฌ')
}
}jest.mock('../moduleA', () => mockFunctions()); const moduleA = require('../moduleA');
describe('๋ชจ์˜ ํ•จ์ˆ˜', () => {

it('t-0', () => {
๊ธฐ๋Œ€(jest.isMockFunction(moduleA.genName)).toBeTruthy();
})

it('t-1', () => {

expect(moduleA.genName(1)).toBe('emilie');
expect(moduleA.genName).toHaveBeenCalled();
expect(moduleA.genName.mock.calls.length).toBe(1);
expect(moduleA.getMessage(1)).toBe('Her name is emilie');
expect(moduleA.genName.mock.calls.length).toBe(2);

});

});

๊ฒ€์‚ฌ ๊ฒฐ๊ณผ:

FAIL jest-examples/__test__/mock-function-0.spec.ts
โ— ๋ชจ์˜ ๊ธฐ๋Šฅ โ€บ t-1

expect(received).toBe(expected)

Expected value to be (using ===):
  "Her name is emilie"
Received:
  "Her name is novaline"

  at Object.it (jest-examples/__test__/mock-function-0.spec.ts:22:35)
  at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)

๋ชจ์˜ ํ•จ์ˆ˜
โœ“ t-0(1ms)
โœ• t-1(22ms)

ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ: 1๊ฐœ ์‹คํŒจ, ์ด 1๊ฐœ
ํ…Œ์ŠคํŠธ: 1๊ฐœ ์‹คํŒจ, 1๊ฐœ ํ†ต๊ณผ, ์ด 2๊ฐœ
์Šค๋ƒ…์ƒท: ์ด 0๊ฐœ
์‹œ๊ฐ„: 0.215์ดˆ, ์˜ˆ์ƒ 1์ดˆ

โ€”
๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธ
https://github.com/facebook/jest/issues/936#issuecomment-305091749 ๋˜๋Š” ์Œ์†Œ๊ฑฐ
์Šค๋ ˆ๋“œ
https://github.com/notifications/unsubscribe-auth/AEeBdv6SafXlTtKo3DNeFWhbL6gV9l0Gks5r_QHjgaJpZM4IPGAH
.

@ainesophaur : export class Session { } ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์ € ์œ„ ๋‚ด ๋Œ“๊ธ€์—์œ„ํ•œ์€ ํ•ด๋‹น ์ž‘ํ’ˆ์— ์ ‘๊ทผ function ๊ตฌ๋ฌธ ๋Œ€์‹  ํ™”์‚ดํ‘œ์˜ ์‚ฌ์šฉ () => . ์—ฌ๊ธฐ:

export const session = {
   saveSession: function() {
      this.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

์ด๊ฒƒ์€ Jest 20.0.3์— ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ํ•˜๋Š” ์ผ์€ ํ•จ์ˆ˜์— ๋Œ€ํ•œ const ๋ž˜ํผ๋ฅผ ๋งŒ๋“  ๋‹ค์Œ ํ•ด๋‹น ๋ž˜ํผ๋ฅผ ๋‚ด๋ณด๋‚ด๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค(์˜ˆ: const fns ๋‚ด๋ณด๋‚ด๊ธฐ). ๊ทธ๋Ÿฐ ๋‹ค์Œ ๋ชจ๋“ˆ ๋‚ด๋ถ€์—์„œ fns.functionName์„ ์‚ฌ์šฉํ•˜๊ณ  fns.functionName ํ•จ์ˆ˜๋ฅผ jest.fn()ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

typescript๋กœ ์ž‘์„ฑ๋œ ์‚ฌ์šฉ์ž ์ •์˜ ๋ชจ๋“ˆ์˜ mock ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๊ฐ€ ํ˜ธ์ถœํ•  ๋•Œ mock ํ•จ์ˆ˜๋Š” ํ•ด๋‹น ํ•จ์ˆ˜์˜ mocked ๋ฒ„์ „์„ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— Coverage ๋ณด๊ณ ์„œ์—์„œ ๋‹ค๋ฃจ๋Š” ์›๋ž˜ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ์—์„œ ์›๋ž˜ ๊ฐ€์ ธ์˜จ 2๊ฐœ์˜ ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
import { getCurrentDate, getStartEndDatesForTimeFrame } from ./../_helpers/date';
๋ณด์‹œ๋‹ค์‹œํ”ผ getStartEndDatesForTimeFrame ๋Š” getCurrentDate ์— ์ข…์†๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋ฉด getCurrentDate ํ…Œ์ŠคํŠธ๊ฐ€ ์ž˜ ์ž‘๋™ํ•˜๊ณ  ๋ชจ์˜ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด์— ์–ด๋–ค ์ด์œ ๋กœ getStartEndDatesForTimeFrame ํ…Œ์ŠคํŠธ๋Š” ์กฐ๋กฑ๋œ getCurrentDate ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ์›๋ž˜ ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. Date.now = jest.fn(() => "2017-11-16T20:33:09.071Z"); ์™€ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์„ค์ •์„ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์•„์ด๋””์–ด๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

export const getCurrentDate = () => new Date();
export const getStartEndDatesForTimeFrame = (timeFrame) => {
  ...
  const todayDate = getCurrentDate();
  ...
  switch (timeframe) {
    case TimeFrames.TODAY:
      console.log(todayDate); // this always prints the real value in tests instead of the mocked one
      start = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate(), 0, 0, 0);
      end = new Date(
        todayDate.getFullYear(),
        todayDate.getMonth(),
        todayDate.getDate(), 23, 59, 59,
      );
      break;
  ...
  return { start: start.toISOString(), end: end.toISOString() }
};
function mockFunctions() {
  const original = require.requireActual('../../_helpers/date');
  return {
    ...original,
    getCurrentDate: jest.fn(() => '2017-11-16T20:33:09.071Z'),
  }
}
jest.mock('../../_helpers/date', () => mockFunctions());
const dateModule = require.requireMock('../../_helpers/date');

describe('getCurrentDate', () => {
  it('returns the mocked date', () => {
    expect(dateModule.getCurrentDate()).
      toBe('2017-11-16T20:33:09.071Z'); // this works well and returns the mocked value
  });
});

describe('getStartEndDatesForTimeFrame', () => {
  it('returns the start and end dates for today', () => {
    expect(dateModule.getStartEndDatesForTimeFrame('today')).toEqual(
      { 'start': '2017-11-15T23:00:00.000Z', 'end': '2017-11-16T22:59:59.000Z' }
    ); // this one uses the original getCurrentDate instead of the mocked one :(
  });
});

๋”ฐ๋ผ์„œ getStartEndDatesForTimeFrame ๋Š” ์กฐ๋กฑ๋œ ์‹œ๊ฐ„์ด ์•„๋‹Œ ํ˜„์žฌ ์‹œ๊ฐ„์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

@ainesophaur ์˜ ์ œ์•ˆ์—

// imageModel.js
const model = {
  checkIfImageExists,
  getImageUrl,
  generateImagePreview
}
export default model

async function checkIfImageExists(...) {}
async function getImageUrl() {}
async function generateImagePreview() {
  // I am calling it as `model` object's method here, not as a locally scoped function
  return model.getImageUrl(...)
}

// imageModel.test.js
import imageModel from './imageModel'

test('should mock getImageUrl called within the same file', async () => {
  imageModel.getImageUrl = jest.fn().mockReturnValueOnce(Promise.resolve())

  await imageModel.generateImagePreview()

  expect(imageModel.getImageUrl).toBeCalled()
})

@miluoshi ์ €๋„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ๋•Œ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋˜๊ฑฐ๋‚˜ ์ด์™€ ์œ ์‚ฌํ•œ ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๊นŒ? ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ "์ž˜๋ชป๋œ" ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •๋ง ์›ํ•ฉ๋‹ˆ๋‹ค.
jest.mock('src/folder/file.func, () => {return 'whatever i want'})

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ .func

@miluoshi @Rdlenke ์ฝ”๋“œ๊ฐ€ ๋ช…๋ช…๋œ ๋‚ด๋ณด๋‚ด๊ธฐ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉด import * as model ๋ฅผ ํ•œ ๋‹ค์Œ model.generateImagePreview = jest.fn(() => Promise.resolve); ๋ฅผ ๋ฎ์–ด์“ธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์„ sinon์œผ๋กœ ์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ์•ž์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด(https://github.com/facebook/jest/issues/936#issuecomment-214939935 ์ฐธ์กฐ) ESM ์ž‘๋™ ๋ฐฉ์‹์€ func2 ๋‚ด์—์„œ func1 func2 ๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์„ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ๋ฐ˜๋“œ์‹œ ๊ธฐ๋ณธ์ด๋ผ๊ณ  ๋ถ€๋ฅด์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.

"testImport" ํ•จ์ˆ˜์—์„œ ์ฝ๋Š” babel ๋ชจ๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜๊ธฐ ์ „์— ๋ชจ๋“ˆ์˜ ๊ธฐ๋Šฅ์„ ๋‚ด๋ณด๋‚ด๋„๋ก ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
ํ…Œ์ŠคํŠธ ์‹คํ–‰?

2017๋…„ 12์›” 18์ผ ์›”์š”์ผ ์˜คํ›„ 5์‹œ์— Jim Moody [email protected]์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ผ์Šต๋‹ˆ๋‹ค.

๋‹น์‹  ๋ง์ด ๋งž์•„์š” @SimenB https://github.com/simenb , ๋‚ด๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ๋ฐ”๊ฟจ์Šต๋‹ˆ๋‹ค
๋‚ด ํ…Œ์ŠคํŠธ์—์„œ Sinon์œผ๋กœ ์ „ํ™˜ํ•˜๋Š” ์‚ฌ์ด์— ํ†ต๊ณผํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค.
๋˜๋Œ๋ ธ์„ ๋•Œ ์—ฌ์ „ํžˆ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๊ฐ€ ์•„๋‹Œ๊ฑฐ ๊ฐ™์€๋ฐ
ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

โ€”
๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธ
https://github.com/facebook/jest/issues/936#issuecomment-352488400 ๋˜๋Š” ์Œ์†Œ๊ฑฐ
์Šค๋ ˆ๋“œ
https://github.com/notifications/unsubscribe-auth/AQRY9a5-s2_bjCWKNw5WiAJW-JeBf8W3ks5tBpoygaJpZM4IPGAH
.

--

๋Œ€๋Ÿฐ ํฌ๋ ˆ์Šค์›ฐ
๊ณ„์•ฝ ๊ฐœ๋ฐœ์ž | ๊ฐœ๋ฐœ์ž ์ œํ•œ
์ด๋ฉ”์ผ: [email protected]
ํ•ธ๋“œํฐ:
์›น์‚ฌ์ดํŠธ: http://www.develer.co.uk

์ด ์ „์ž ๋ฉ”์ผ์„ ์ธ์‡„ํ•˜๊ธฐ ์ „์— ํ™˜๊ฒฝ์„ ์ƒ๊ฐํ•˜์‹ญ์‹œ์˜ค
๊ฒฝ๊ณ : ์ปดํ“จํ„ฐ ๋ฐ”์ด๋Ÿฌ์Šค๋Š” ์ด๋ฉ”์ผ์„ ํ†ตํ•ด ์ „์†ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ›๋Š” ์‚ฌ๋žŒ
์ด ์ด๋ฉ”์ผ๊ณผ ๋ชจ๋“  ์ฒจ๋ถ€ ํŒŒ์ผ์— ๋ฐ”์ด๋Ÿฌ์Šค๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
Developer Limited๋Š” ๋ฐ”์ด๋Ÿฌ์Šค๋กœ ์ธํ•œ ํ”ผํ•ด์— ๋Œ€ํ•ด ์ฑ…์ž„์„ ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ์ด๋ฉ”์ผ ์ „์†ก์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†์Œ
์ •๋ณด๊ฐ€ ๊ฐ€๋กœ์ฑ„๊ฑฐ๋‚˜, ์†์ƒ๋˜๊ฑฐ๋‚˜, ์†์‹ค๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์•ˆ์ „ํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
ํŒŒ๊ดด๋˜๊ฑฐ๋‚˜ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•˜๊ฑฐ๋‚˜ ๋ถˆ์™„์ „ํ•˜๊ฒŒ ๋„์ฐฉํ•˜๊ฑฐ๋‚˜ ๋ฐ”์ด๋Ÿฌ์Šค๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋ฐœ์‹ ์ธ
๋”ฐ๋ผ์„œ ๋ณธ ๋ฌธ์„œ์˜ ์˜ค๋ฅ˜๋‚˜ ๋ˆ„๋ฝ์— ๋Œ€ํ•ด ์ฑ…์ž„์„ ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์ด๋ฉ”์ผ ์ „์†ก์˜ ๊ฒฐ๊ณผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์ด ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ.

๊ฒฝ๊ณ : Develer Limited๊ฐ€ ํ•ฉ๋‹นํ•œ ์˜ˆ๋ฐฉ ์กฐ์น˜๋ฅผ ์ทจํ–ˆ์ง€๋งŒ
์ด ์ด๋ฉ”์ผ์— ๋ฐ”์ด๋Ÿฌ์Šค๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. ํšŒ์‚ฌ๋Š” ์ˆ˜๋ฝํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ ๋˜๋Š”
์ฒจ๋ถ€ ํŒŒ์ผ.

Developer Limited๋Š” ์ž‰๊ธ€๋žœ๋“œ์™€ ์›จ์ผ์ฆˆ์— ๋“ฑ๋ก๋œ ์œ ํ•œ ํšŒ์‚ฌ์ž…๋‹ˆ๋‹ค. |
์‚ฌ์—…์ž๋“ฑ๋ก๋ฒˆํ˜ธ 09817616 | ๋“ฑ๋ก ์‚ฌ๋ฌด์‹ค: SUITE 1 SECOND
FLOOR EVERDENE HOUSE, DEANSLEIGH ROAD, ๋ณธ๋จธ์Šค, ์˜๊ตญ, BH7 7DU

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด @ainesophaur ์—๊ฒŒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

๋ˆ„๊ตฌ๋“ ์ง€ ๋น„๋™๊ธฐ์‹ ์ž‘์—…์ด ์•„๋‹Œ ์˜ˆ์ œ๊ฐ€ ์œ ์šฉํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ๊ฒฝ์šฐ ์—ฌ๊ธฐ์— ๋‚ด ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

//reportError.js
const functions = {};

functions._reportToServer = (error, auxData) => {
  // do some stuff
};

functions.report = (error, auxData = {}) => {
  if (shouldReportToServer()) {
    functions._reportToServer(error, auxData);
  }
};
export default functions;

// reportError.jest.js
import reportError from 'app/common/redux/lib/reportError';
reportError._reportToServer = jest.fn();

describe('test reportError', () => {
  it('reports ERROR to server as as error', () => {
   reportError.report(new Error('fml'), {});
    expect(reportError._reportToServer).toHaveBeenCalledTimes(1);
  });
});

@jim-moody ๋ฌธ์ œ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ดํ•ดํ–ˆ๋‹ค๋ฉด ๊ท€ํ•˜์˜ ์˜ˆ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

const spyOnExampleFunc2 = jest.spyOn(example, 'func2');
example.func1();
expect(spyOnExampleFunc2).toBeCalled();

(์ด _only_๋Š” ์˜ˆ์ œ์™€ ๊ฐ™์ด ํ•จ์ˆ˜๋ฅผ const๋กœ ๋‚ด๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.)

@dinvlad ๋‚˜์˜ ์˜์›…!

@dinvlad ์˜ ๋‹ต๋ณ€์— ๊ฐ์ฒด ํŽ˜์ด์ง€ ์˜ ๋‹ค์Œ ๋ชจ์˜ ๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ์˜ˆ์‹œ๋กœ ๋ณด์—ฌ์ฃผ๊ฑฐ๋‚˜, ๋ชจ์˜ ๊ธฐ๋Šฅ ํŽ˜์ด์ง€ ์— ์—ฐ๊ฒฐํ•˜๋ฉด

  • jest.isMockFunction(fn)
  • jest.genMockFromModule(๋ชจ๋“ˆ ์ด๋ฆ„)
  • jest.mock(๋ชจ๋“ˆ ์ด๋ฆ„, ๊ณต์žฅ, ์˜ต์…˜)
  • jest.unmock(๋ชจ๋“ˆ ์ด๋ฆ„)
  • jest.doMock(๋ชจ๋“ˆ ์ด๋ฆ„, ๊ณต์žฅ, ์˜ต์…˜)
  • jest.dontMock(๋ชจ๋“ˆ ์ด๋ฆ„)

๋‚ด ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” jest์˜ ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋กœ์„œ ์ผ๋ถ€ mocha + sinon.js ์ฝ”๋“œ๋ฅผ jest๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ ๊ฐ„์ฒฉ๊ณผ ๊ธฐ๋Œ€๊ฐ€ ์žˆ์—ˆ๊ธฐ์— ์‰ฌ์šธ ์ค„ ์•Œ์•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ์Šค๋ ˆ๋“œ๋ฅผ ์ฝ๊ณ  Mock ํ•จ์ˆ˜์— ๋Œ€ํ•œ jest ๋ฌธ์„œ๋ฅผ ์ฝ์€ ํ›„ ์ €๋Š” jest๋ฅผ ์ด๋Ÿฐ ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ESM ๋˜๋Š” Babel์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ดํ•ด๊ฐ€ ํ•„์š”ํ•˜๊ฑฐ๋‚˜ ๊ธฐํƒ€ ํ˜ผ๋ž€์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ธ์ƒ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.

Jest ๋•๋ถ„์— ํ…Œ์ŠคํŠธ๋ฅผ ๋” ์‰ฝ๊ฒŒ ์ž‘์„ฑ/์ดํ•ดํ•˜๊ณ  ๋” ๋น ๋ฅด๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. :)

๋ฌธ์„œ๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๋Š” PR์€ ๋งค์šฐ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! ๐Ÿ™‚

ES ๋ชจ๋“ˆ ๊ตฌ๋ฌธ์œผ๋กœ ํŠน์ • ๋ชจ๋“ˆ๋งŒ ๋ชจ์˜ํ•˜๋ ค๋ฉด require.requireActual ์„ ์‚ฌ์šฉํ•˜์—ฌ ์›๋ž˜ ๋ชจ๋“ˆ์„ ๋ณต์›ํ•œ ๋‹ค์Œ ๋ชจ์˜ํ•˜๋ ค๋Š” ๋ชจ๋“ˆ์„ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { foo } from './example';

jest.mock('./example', () => (
  ...require.requireActual('./example'),
  foo: jest.fn()
));

test('foo should be a mock function', () => {
  expect(foo('mocked!')).toHaveBeenCalledWith('mocked!');
});

๊ฑฐ๊พธ๋กœ ๋œ ๋Š๋‚Œ์ด ๋“ค์ง€๋งŒ ๋‚ด๊ฐ€ ๋งŒ๋‚œ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. @joshjg์— ๋Œ€ํ•œ ๋ชจ์ž ํŒ.

๊ธด ํ† ๋ก ์—์„œ ๊ธธ์„ ์žƒ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐฉ๊ธˆ ์งˆ๋ฌธ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด์จŒ๋“  ํ•จ์ˆ˜์˜ ์‹ค์ œ ๊ตฌํ˜„์ด ํ˜ธ์ถœ๋˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๋‚ด๊ฐ€ ์ดํ•ดํ•˜๋Š” ๋ฐ”์— ๋”ฐ๋ฅด๋ฉด jest.fn() ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์›๋ž˜ ๊ธฐ๋Šฅ์„ ์žฌ์ •์˜ํ•˜์ง€๋งŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์ฝ˜์†”์—์„œ jest.fn() function or a spy ์—ฌ์•ผ ํ•œ๋‹ค๋Š” ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์š”์ฒญ์ด ์ „๋‹ฌ๋  ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๊ณ  ํ•˜๋ฏ€๋กœ ์กฐ๋กฑํ•˜๋ฉด ๋ชจ๋“  ๋…ผ๋ฆฌ๊ฐ€ ์†์‹ค๋˜๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์กฐ๋กฑํ•˜์ง€ ์•Š๊ณ  ๊ฐ€์ ธ์˜ค๋ฉด ์ด ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

jest.spyOn ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ธฐ๋ณธ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

๋„์›€์„ ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์‹œ๋„ํ–ˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ console.log๋ฅผ ๋„ฃ๊ณ  ์ธ์‡„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ˜ธ์ถœ๋˜์—ˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ์— ๋”ฐ๋ฅด๋ฉด ํ˜ธ์ถœ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ํŒŒ์ผ

import errorHandler from '../../controller/errorHandler'

describe('auth test', () => {
  describe('test error: ', () => {
    const test1 = jest.spyOn(errorHandler, 'handleClientError')
    test('should return 400', (done) => {
      request(app)
      .post('/auth/error')
      .then((res) => {
        expect(res.statusCode).toBe(400)
        expect(test1).toBeCalled()
        done()
      })
    })

์˜ค๋ฅ˜ ํ•ธ๋“ค๋Ÿฌ

module.exports = {
  handleClientError () {
    console.log('err')
  }
}

์ฝ˜์†”

console.log src/controller/errorHandler.js:10
      err

  โ— auth test โ€บ test error:  โ€บ  should return 400

    expect(jest.fn()).toBeCalled()

    Expected mock function to have been called.

      18 |         expect(res.statusCode).toBe(400)
    > 19 |         expect(test1).toBeCalled()
      20 |         done()
      21 |       })
      22 |     })

ํ•จ์ˆ˜๊ฐ€ handleClientError ๋˜๋Š” logError ์ž…๋‹ˆ๊นŒ?

@WangHansen ๊ท€ํ•˜์˜ ์˜ˆ์ œ์—์„œ ๊ท€ํ•˜์˜ ์ฝ”๋“œ๋Š” expect(errorHandler.handleClientError).toBeCalled() // > true ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@WangHansen .mockImplementation() ์„ jest.spyOn() ์žˆ์Šต๋‹ˆ๊นŒ? Jasmine์—์„œ ์˜จ ์‚ฌ๋žŒ์œผ๋กœ์„œ ์ €๋Š” ์ด ํŒ์ด Jasmine์˜ ์ŠคํŒŒ์ด์™€ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ๋‹ฌ์„ฑํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค. ์˜ˆ

const mockModuleFunction = jest
  .spyOn(module, 'function')
  .mockImplementation(() => 'hello');
...
expect(mockModuleFunction.mock).toBeCalled();

mockImplementation() ์‚ฌ์šฉํ•˜์ง€ _์•Š์œผ๋ฉด_ jest.spyOn() ๋Š” ๋ชจ์˜(afaiu)๊ฐ€ _์•„๋‹Œ_ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์‹ค์ œ๋กœ ๊ธฐ๋ณธ ๊ตฌํ˜„์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๊ตฌํ˜„์„ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const moduleFunction = module.function;
jest.spyOn(module, 'function').mockImplementation(moduleFunction);
...

์ด๊ฒƒ์ด ๊ผญ ํ•„์š”ํ•œ์ง€๋Š” ํ™•์‹คํ•˜์ง€ ์•Š์ง€๋งŒ _๋ฐ˜๋“œ์‹œ_ ์ž‘๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์›๋ž˜ ์š”์ฒญ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ...

import * ์ฃผ์œ„์— ํ”„๋ก์‹œ๋ฅผ ๋ž˜ํ•‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๊นŒ? ์˜ˆ

'./myfile.js'์—์„œ ํ…Œ์ŠคํŠธ๋กœ * ๊ฐ€์ ธ์˜ค๊ธฐ;

const ํ•ธ๋“ค๋Ÿฌ = {
/** ๊ฐ€๋กœ์ฑ„๊ธฐ: ์†์„ฑ ๊ฐ€์ ธ์˜ค๊ธฐ */
get(๋Œ€์ƒ, propKey, ์ˆ˜์‹ ๊ธฐ) {
console.log( GET ${propKey} );
๋ฐ˜ํ™˜ 123;
},

/** Intercepts: checking whether properties exist */
has(target, propKey) {
    console.log(`HAS ${propKey}`);
    return true;
}};

const p = ์ƒˆ๋กœ์šด ํ”„๋ก์‹œ(ํ…Œ์ŠคํŠธ);

2018๋…„ 1์›” 30์ผ ํ™”์š”์ผ ์˜คํ›„ 4:24 Denis Loginov [email protected]
์ผ๋‹ค:

@WangHansen https://github.com/wanghansen ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
.mockImplementation() to your jest.spyOn()? ์—์„œ ์˜ค๋Š” ์‚ฌ๋žŒ์œผ๋กœ์„œ
Jasmine, ๋‚˜๋Š” ์ด ํŒ์ด
์žฌ์Šค๋ฏผ์˜ ์ŠคํŒŒ์ด. ์˜ˆ

const mockModuleFunction = ๋†๋‹ด
.spyOn(๋ชจ๋“ˆ, 'ํ•จ์ˆ˜')
.mockImplementation(() => '์•ˆ๋…•ํ•˜์„ธ์š”');...expect(mockModuleFunction.mock).toBeCalled();

mockImplementation()์„ ์‚ฌ์šฉ ํ•˜์ง€ ์•Š์œผ๋ฉด jest.spyOn()์ด ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
๋ชจ์˜(afaiu)๊ฐ€ ์•„๋‹Œ ๊ฐ์ฒด์ด๋ฉฐ ์‹ค์ œ๋กœ ๋„ค์ดํ‹ฐ๋ธŒ
๊ตฌํ˜„. ๋„ค์ดํ‹ฐ๋ธŒ ๊ตฌํ˜„์„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์•„๋งˆ๋„
์‚ฌ์šฉํ•  ๊ฐ€์น˜

const moduleFunction = module.function;jest.spyOn(๋ชจ๋“ˆ, 'ํ•จ์ˆ˜').mockImplementation(moduleFunction);...

์ด๊ฒƒ์ด ํ•„์š”ํ•œ์ง€๋Š” ํ™•์‹คํ•˜์ง€ ์•Š์ง€๋งŒ ์ž‘๋™ ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ํ™•์‹คํ•ฉ๋‹ˆ๋‹ค.

โ€”
๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธ
https://github.com/facebook/jest/issues/936#issuecomment-361648414 ๋˜๋Š” ์Œ์†Œ๊ฑฐ
์Šค๋ ˆ๋“œ
https://github.com/notifications/unsubscribe-auth/AQRY9VXyHNYatwOOY6EV637WGQH9k5Plks5tP0I9gaJpZM4IPGAH
.

--

๋Œ€๋Ÿฐ ํฌ๋ ˆ์Šค์›ฐ
๊ณ„์•ฝ ๊ฐœ๋ฐœ์ž | ๊ฐœ๋ฐœ์ž ์ œํ•œ
์ด๋ฉ”์ผ: [email protected]
ํ•ธ๋“œํฐ:
์›น์‚ฌ์ดํŠธ: http://www.develer.co.uk

์ด ์ „์ž ๋ฉ”์ผ์„ ์ธ์‡„ํ•˜๊ธฐ ์ „์— ํ™˜๊ฒฝ์„ ์ƒ๊ฐํ•˜์‹ญ์‹œ์˜ค
๊ฒฝ๊ณ : ์ปดํ“จํ„ฐ ๋ฐ”์ด๋Ÿฌ์Šค๋Š” ์ด๋ฉ”์ผ์„ ํ†ตํ•ด ์ „์†ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ›๋Š” ์‚ฌ๋žŒ
์ด ์ด๋ฉ”์ผ๊ณผ ๋ชจ๋“  ์ฒจ๋ถ€ ํŒŒ์ผ์— ๋ฐ”์ด๋Ÿฌ์Šค๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
Developer Limited๋Š” ๋ฐ”์ด๋Ÿฌ์Šค๋กœ ์ธํ•œ ํ”ผํ•ด์— ๋Œ€ํ•ด ์ฑ…์ž„์„ ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ์ด๋ฉ”์ผ ์ „์†ก์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†์Œ
์ •๋ณด๊ฐ€ ๊ฐ€๋กœ์ฑ„๊ฑฐ๋‚˜, ์†์ƒ๋˜๊ฑฐ๋‚˜, ์†์‹ค๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์•ˆ์ „ํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
ํŒŒ๊ดด๋˜๊ฑฐ๋‚˜ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•˜๊ฑฐ๋‚˜ ๋ถˆ์™„์ „ํ•˜๊ฒŒ ๋„์ฐฉํ•˜๊ฑฐ๋‚˜ ๋ฐ”์ด๋Ÿฌ์Šค๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋ฐœ์‹ ์ธ
๋”ฐ๋ผ์„œ ๋ณธ ๋ฌธ์„œ์˜ ์˜ค๋ฅ˜๋‚˜ ๋ˆ„๋ฝ์— ๋Œ€ํ•ด ์ฑ…์ž„์„ ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์ด๋ฉ”์ผ ์ „์†ก์˜ ๊ฒฐ๊ณผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์ด ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ.

๊ฒฝ๊ณ : Develer Limited๊ฐ€ ํ•ฉ๋‹นํ•œ ์˜ˆ๋ฐฉ ์กฐ์น˜๋ฅผ ์ทจํ–ˆ์ง€๋งŒ
์ด ์ด๋ฉ”์ผ์— ๋ฐ”์ด๋Ÿฌ์Šค๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. ํšŒ์‚ฌ๋Š” ์ˆ˜๋ฝํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ ๋˜๋Š”
์ฒจ๋ถ€ ํŒŒ์ผ.

Developer Limited๋Š” ์ž‰๊ธ€๋žœ๋“œ์™€ ์›จ์ผ์ฆˆ์— ๋“ฑ๋ก๋œ ์œ ํ•œ ํšŒ์‚ฌ์ž…๋‹ˆ๋‹ค. |
์‚ฌ์—…์ž๋“ฑ๋ก๋ฒˆํ˜ธ 09817616 | ๋“ฑ๋ก ์‚ฌ๋ฌด์‹ค: SUITE 1 SECOND
FLOOR EVERDENE HOUSE, DEANSLEIGH ROAD, ๋ณธ๋จธ์Šค, ์˜๊ตญ, BH7 7DU

@dinvlad @iampeterbanjo @SimenB ๋ชจ๋“  ๋„์›€์— ๋‹ค์‹œ ํ•œ ๋ฒˆ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•ˆํƒ€๊น๊ฒŒ๋„ ์ œ์•ˆํ•˜์‹  ๋ฐฉ๋ฒ• ์ค‘ ์–ด๋Š ๊ฒƒ๋„ ํšจ๊ณผ๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. next(err) ํ˜•์‹์œผ๋กœ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์ธ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ๋…ผ๋ฆฌ๋Š”, ๊ทธ๊ฒƒ์€์— ์ „๋‹ฌ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค errorHandler ํ˜ธ์ถœํ•˜์—ฌ return next(err) . console.log ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ์ธ์‡„ ์ค‘์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ…Œ์ŠคํŠธ์— ๋”ฐ๋ฅด๋ฉด ๊ฒฐ์ฝ” ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@dinvlad ๋‚˜๋Š” ๋‹น์‹ ์˜ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ ์–ด์จŒ๋“  ๋„์›€์„ ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. jest.spyOn ์˜ ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด ์™œ mockImplementation ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ์›๋ž˜ ํ•จ์ˆ˜๋ฅผ ์žฌ์ •์˜ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

@WangHansen ์˜ˆ, ์›๋ž˜ ๋ฐฉ๋ฒ•์„ ์žฌ์ •์˜ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์ด ๋งž์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋‹จ์ง€ ๊ทธ ์ƒํ™ฉ์— ๋Œ€ํ•œ ์•„์ด๋””์–ด๋ฅผ ๋˜์ง€๊ณ  ์žˆ์—ˆ๋‹ค.

์‹คํŒจํ–ˆ์„ ์ˆ˜ ์žˆ๋Š” ํ•œ ๊ฐ€์ง€ ์ด์œ ๋Š” ๋น„๋™๊ธฐ์„ฑ์ž…๋‹ˆ๋‹ค. ๋ฉ”์„œ๋“œ๊ฐ€ ์ฝœ๋ฐฑ ๋ฐ/๋˜๋Š” ์•ฝ์†(๋˜๋Š” async/await)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ์ข…๋ฃŒ๋˜๊ธฐ ์ „์— ์˜ˆ์ƒ์ด ์‹ค์ œ๋กœ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์„ ์ฃผ์žฅํ•˜๋Š” ํŠน๋ณ„ํ•œ ๋ฐฉ๋ฒ• expect.assertions(N) ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ฝœ๋ฐฑ/์•ฝ์† ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๊ฐ€ ํ˜ธ์ถœ๋œ ํ›„์—๋งŒ ์˜ˆ์ƒ์ด ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. ๋‚˜๋Š” ๋‹น์‹ ์ด ๊ทธ๊ฒƒ์„ ๋ณด์•˜์„ ๊ฒƒ์ด๋ผ๊ณ  ํ™•์‹ ํ•˜์ง€๋งŒ ์ฐธ์กฐ ์šฉ์œผ๋กœ https://facebook.github.io/jest/docs/en/asynchronous.html

@seibelj๊ฐ€ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” fns๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์€ ๋ชจ๋“ˆ impl์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๋‚ด ์ƒ๊ฐ์— ํ…Œ์ŠคํŠธ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๊ตฌํ˜„๋˜๋Š” ๋ฐฉ์‹์„ ์ฃผ๋„ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค ๐Ÿ˜• (์ ์–ด๋„ ์ด ์ •๋„๊นŒ์ง€๋Š” ์•„๋‹˜)

๋†๋‹ด์œผ๋กœ ๊ทธ๋Ÿฌํ•œ ํ–‰๋™์„ ๊ตฌํ˜„ํ•  ๊ณ„ํš์ด ์žˆ์Šต๋‹ˆ๊นŒ?

์•ˆ๋…•ํ•˜์„ธ์š”,
์ „์ฒด ํ† ๋ก ์— ๋‹ค์†Œ ๋Šฆ์—ˆ์ง€๋งŒ ์ „์ฒด ํ† ๋ก ์„ ํ†ตํ•ด ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค. ์•„์ง๊นŒ์ง€๋Š” ๊ทธ๋ ‡๊ฒŒ ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.
@greypants ์˜ ์œ ๋งํ•œ ์†”๋ฃจ์…˜์€ ์—ฌ์ „ํžˆ โ€‹โ€‹์›๋ž˜ ๊ธฐ๋Šฅ์„ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
์ด ํ† ๋ก ์„ ์‹œ์ž‘ํ•œ ์ดํ›„๋กœ ๋ณ€๊ฒฝ๋œ ์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚ด๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ๋†“์น˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

๋‚˜๋Š” @greypants ์˜ ์†”๋ฃจ์…˜์„ ์•ฝ๊ฐ„ ์ˆ˜์ •ํ–ˆ๊ณ  ๊ทธ๊ฒƒ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ˆ„๊ตฐ๊ฐ€๋ฅผ ๋„์šธ ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋‚ด ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import Module from './module'
import { getJSON } from './helpers'

jest.mock('./helpers', () =>
  Object.assign(require.requireActual('./helpers'), {
    getJSON: jest.fn()
  })
)

test('should use the mock', async () => {
  getJSON.mockResolvedValue('foo')
  await expect(module().someAsyncThingThatUsesGetJSON()).resolves.toEqual('foo')
})

test('should use the actual module', () => {
  expect(module().someFunctionThatUsesAHelper()).toBe(true)
})

์ด๊ฒƒ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹์ข€ ํ•ดํ‚คํ•˜๊ณ  ๋ฌธ์„œ๊ฐ€ ๋„์›€์ด๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. Jest์™€ ๊ทธ ๋’ค์— ์žˆ๋Š” ํŒ€์— ๊ฐ์‚ฌํ•˜์ง€๋งŒ ์ตœ์†Œํ•œ "๊ณต์‹์ ์ธ" ์†”๋ฃจ์…˜์ด ์žˆ์–ด์•ผ ํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค.

@sarahdayan , ๊ทธ๋ฆฌ๊ณ  ๊ด€์‹ฌ ์žˆ๋Š” ์‚ฌ๋žŒ -
๋‚˜๋Š” babel-plugin-rewire๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ฐพ๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ์ง€๋งŒ 'ํ•ต์‹ฌ์ด๋ผ๊ณ  ๋Š๋ผ์ง€ ์•Š์„ ๋งŒํผ ์ผ๊ด€๋œ ์†”๋ฃจ์…˜์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์šฐ๋ฆฌ๋Š” ๋ชจ๋“ˆ์—์„œ ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ธฐ๋Šฅ์„ ๋ชจ์˜ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค . global jest์˜ ์กฐ๋กฑ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ genMockFromModule ๋ฐ requireActual ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

//  __mocks__/myModule.js
const moduleMock = jest.genMockFromModule('./../myModule');

// in most cases we don't want to mock `someFunction`
moduleMock.someFunction = jest.fn(
  (...args) => require.requireActual('./../myModule').someFunction(...args)
)

module.exports = moduleMock;

์ด ์†”๋ฃจ์…˜์€ ์ „์ฒด ๋ชจ๋“ˆ์ด ๋ชจ์˜๋  ๋•Œ someFunction ์›๋ž˜ ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“ˆ์˜ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๋ชจ์˜ ์‚ฌ์šฉ์„ ํ—ˆ์šฉํ•˜๊ณ  mockImplementationOnce ๋˜๋Š” mockImplementation ํ•˜์—ฌ someFunction ํ•จ์ˆ˜๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค mockImplementation API.

์œ„์˜ ๋ชจ๋“  ๋Œ€ํ™”๋ฅผ ์ฝ์—ˆ์ง€๋งŒ ๋‚˜์—๊ฒŒ ๋งž๋Š” ์†”๋ฃจ์…˜์€ ์—†์Šต๋‹ˆ๋‹ค.
์ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ๋Œ€ํ•œ ์†”๋ฃจ์…˜์„ ์—ฌ์ „ํžˆ ์ฐพ๊ณ  ์žˆ๋‹ค๋ฉด, ๋Œ€๋‹ต์€ babel-plugin-rewire ์ž…๋‹ˆ๋‹ค . ์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์šฐ๋ฆฌ๊ฐ€ ๋…ผ์˜ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค ์ผ€์ด์Šค๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๊ทธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ดํŽด๋ณด์‹ญ์‹œ์˜ค. ๋‚˜์ค‘์— ๋‹ค์‹œ ๊ฐ์‚ฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์œ„์˜ ์ „์ฒด ์Šค๋ ˆ๋“œ๋ฅผ ์š”์•ฝํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. f , g ๋ฐ h ํ•จ์ˆ˜๊ฐ€ ์žˆ๋Š” m ๋ชจ๋“ˆ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ g ๋ฐ h ํ˜ธ์ถœ f . ์šฐ๋ฆฌ๋Š” g ์™€ h ๊ฐ€ ์‹ค์ œ f ๋Œ€์‹ ์— mock์„ ํ˜ธ์ถœํ•˜๋„๋ก f ๋ฅผ ๋ชจ์˜ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋ถˆํ–‰ํ•˜๊ฒŒ๋„์ด ์ง์ ‘ํ•˜์ง€ ์•Š๋Š” ํ•œ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค f ํ•ญ์ƒ ํ†ตํ•ด์ด๋ผ๊ณ  exports ๋กœ cpojer ์„ค๋ช… . ์ด๊ฒƒ์€ ๋ชจ๋“ˆ์ด TypeScript์—์„œ ES6 ๊ฐ€์ ธ์˜ค๊ธฐ/๋‚ด๋ณด๋‚ด๊ธฐ ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค(๋ฐ”๋ฒจ์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค).
  2. ๊ทธ๋Ÿฌ๋‚˜ f ๋ฅผ ๋‹ค๋ฅธ ๋ชจ๋“ˆ m2 ๋กœ ์ด๋™ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด m ๋Š” import {f} from 'm2' ์™€ ๊ฐ™์€ ๋ช…๋ น๋ฌธ์„ ๊ฐ–๊ฒŒ ๋˜๋ฉฐ g ๋ฐ h ๊ฐ€ f ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์‹ค์ œ๋กœ๋Š” m2.f ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค m2 = require('./m2') (๋ฐ”๋ฒจ/TypeScript ๋ฒˆ์—ญ์ด ํ‘œ์‹œ๋˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค). ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด greypants ์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ f ์•ˆ์ •์ ์œผ๋กœ ์กฐ๋กฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋ชจ๋“ˆ ๊ฒฝ๊ณ„๋ฅผ ๋„˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ˜ธ์ถœ์„ ์•ˆ์ •์ ์œผ๋กœ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค . ์ฐธ๊ณ : greypants์˜ ์†”๋ฃจ์…˜์€ ์ด์ œ " jest.mock() ์˜ ๋ชจ๋“ˆ ํŒฉํ† ๋ฆฌ๋Š” ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - ์ž˜๋ชป๋œ ๋ณ€์ˆ˜ ์•ก์„ธ์Šค: __assign"์ด๋ผ๋Š” ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์ด Jest์˜ ๋ฒ„๊ทธ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด Object.assign ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.
  3. ๊ทธ๋Ÿฌ๋‚˜ ํ•˜๋‚˜ ๋˜๋Š” ๋‘ ๊ฐœ์˜ ํ•จ์ˆ˜๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๋Œ€์‹  ํ•˜๋‚˜ ๋˜๋Š” ๋‘ ๊ฐœ์˜ ํ•จ์ˆ˜๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๊ฒฝ์šฐ darkowic๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

(2)์˜ ์˜ˆ:

~~~js
// ๋ชจ๋“ˆ m.js
'./m2'์—์„œ {f} ๊ฐ€์ ธ์˜ค๊ธฐ
๋‚ด๋ณด๋‚ด๊ธฐ ํ•จ์ˆ˜ g() { ๋ฐ˜ํ™˜ 'f ๋ฐ˜ํ™˜ ' + f(); };

// ๋ชจ๋“ˆ m2.js
export function f() { return '์‹ค์ œ f'; }

// ํ…Œ์ŠคํŠธ.js
*๋ฅผ './m'์—์„œ m์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ

jest.mock('./m2', () => Object.assign(
require.requireActual('./m2'), {
f: jest.fn().mockReturnValue('๋ชจ์˜')
}));

test('์กฐ๋กฑ', () => {
๊ธฐ๋Œ€(mg()).toEqual('๋ชจ์˜ ๋ฐ˜ํ™˜');
});
~~~

์ด๊ฒƒ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋™์•ˆ #2649: ํ…Œ์ŠคํŠธ ๋‚ด์—์„œ jest.mock ๋ฅผ ํ˜ธ์ถœํ•ด๋„ ์•„๋ฌด๋Ÿฐ ํšจ๊ณผ๊ฐ€ ์—†์œผ๋ฉฐ ์ „์—ญ ๋ฒ”์œ„์—์„œ ํ˜ธ์ถœํ•˜๋ฉด ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ ์ „์— unmock ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋งค์šฐ ์„ฑ๊ฐ€์‹œ๋‹ค.

๊ฐ์‚ฌ ํ•ด์š”!! @sarahdayan
์ด๊ฒƒ์„ ์ž ์‹œ ๋™์•ˆ ์ฐพ๊ณ  ์žˆ์—ˆ๋‹ค

๋ฌธ์„œ๊ฐ€ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ PR์€ ํ•ญ์ƒ ๋ฌธ์„œ๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๋Š” ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค ๐Ÿ™‚

์•ˆ๋…•ํ•˜์„ธ์š” ์—ฌ๋Ÿฌ๋ถ„!

๋‚˜๋Š” ์กฐ๊ธˆ ๋†€์•˜๊ณ  ๊ทธ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์•„์ด๋””์–ด๋ฅผ ๋ƒˆ์Šต๋‹ˆ๋‹ค.

  • ๋ชจ์˜ ๋ชจ๋“ˆ์ด์ง€๋งŒ ๋ชจ์˜ ๋ชจ๋“ˆ์€ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์— ์›๋ž˜ ๋ชจ๋“ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ชจ์˜ ๋ชจ๋“ˆ์— ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฉ”์„œ๋“œ ์ œ๊ณต(ํ”„๋กœํ† ํƒ€์ž…์˜ ์†์„ฑ์„ ์žฌ์ •์˜ํ•จ)
  • ๋˜ํ•œ ๋ชจ์˜ ๋ชจ๋“ˆ์—์„œ ์†์„ฑ์„ ์ œ๊ฑฐํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค(ํ”„๋กœํ† ํƒ€์ž…์˜ ์†์„ฑ์„ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด).
// m1.js
export const f = () => "original f"

// __mocks__/m1.js
const originalM1 = require.requireActual("../m1");
// set the original as a prototype
let mockModule: any = Object.create(originalM1);
const __setMock = (name, value) => {
  mockModule[name] = value;
};
const __removeMock = (name) => {
  Reflect.deleteProperty(mockModule, name);
};
// enhance the mocked module to allow overriding original exports
module.exports = Object.assign(mockModule, { __setMock, __removeMock });


// m1.test.js
import { f } from "./m1";

jest.mock("./m1");

test("mocking stuff", () => {
  // here nothing is mocked - the original module is used
  expect(f()).toBe("original f");

  // override the export f of the module
  require("./m1").__setMock("f", () => "mocked f");
  expect(f()).toBe("mocked f");

  // set it back to the original
  require("./m1").__removeMock("f");
  expect(f()).toBe("original f");

  //override with another value
  require("./m1").__setMock("f", () => "another mocked f");
  expect(f()).toBe("another mocked f");
});

๋‚ด 2์„ผํŠธ:

๋‚˜๋Š” ๋งŽ์€ ์†”๋ฃจ์…˜์„ ํ…Œ์ŠคํŠธํ–ˆ๊ณ (๋ชจ๋‘๋Š” ์•„๋‹์ง€๋ผ๋„) ๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ•œ ์œ ์ผํ•œ ์†”๋ฃจ์…˜์€ ์ด๊ฒƒ์ž…๋‹ˆ๋‹ค(jest 23).

// importedModule.js
export const funcB = () => {
  return "not mocked";
};
export const funcA = () => {
  return mockProxy.funcB(); // this works but it's soooo hacky
};

export const mockProxy = {
  funcB
};

// moduleToTest.js
import { funcA } from "./moduleImported.js";

export default function() {
  return funcA();
}

// test
let moduleImported = require("./moduleImported.js");
moduleImported.mockProxy.funcB = jest.fn(() => "mocked");
const funcToTest = require("./moduleToTest.js").default; // or import

it("should return mocked", function() {
  expect(funcToTest()).toBe("mocked");
});

๋‚˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ์ด๊ฒƒ์„ ์‹œ๋„ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์ง€ ์•Š๋‹ค๋Š” ๊ฒฐ๋ก ์— ๋„๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค.

  • test.js ๋Š” importedModule.js ์˜ ๊ตฌํ˜„ ์„ธ๋ถ€์ •๋ณด์— ๋Œ€ํ•ด ๋„ˆ๋ฌด ๋งŽ์ด ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด ์†”๋ฃจ์…˜์€ ๊นจ์ง€๊ธฐ์„ํ•˜๊ณ  ์•„๋ฌด๋„์˜ ๋ชฉ์ ์„ ์ดํ•ดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค mockProxy ๋ณด๋ฉด importedModule.js

๋ˆ„๊ตฌ๋“ ์ง€ ์ž‘๋™ํ•˜๋Š” ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์Šต๋‹ˆ๊นŒ?

๋‚˜๋Š” ์‚ฌ์šฉํ•˜๊ณ ์žˆ๋‹ค :

"jest": "^21.2.1",
"jest-cli": "^21.2.1",

@jamesone https://github.com/facebook/jest/issues/936#issuecomment -410080252 ์ฝ์–ด ๋ณด์…จ๋‚˜์š”?

์ด๊ฒƒ์€ @thomaskempel ์˜ ๋‹ต๋ณ€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ €์—๊ฒŒ

์ œ ๊ฒฝ์šฐ์—๋Š” node_modules์—์„œ ์ข…์†์„ฑ์„ ์กฐ๋กฑํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ '๊ณต์œ  ๊ตฌ์„ฑ ์š”์†Œ'๋ผ๊ณ  ๋ถ€๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๋ช…๋ช…๋œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๋Ÿฌํ•œ ๋ช…๋ช…๋œ ๋‚ด๋ณด๋‚ด๊ธฐ ์ค‘ ๋ช‡ ๊ฐœ๋งŒ ์กฐ๋กฑํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ง„์งœ๋กœ ๋‚จ๊ฒจ๋‘๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ __mocks__/shared-components.js ์—๋Š” ๋‹ค์Œ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

const original = require.requireActual('shared-components');

module.exports = {
...original,
moduleNameToOverride: jest.fn().mockImplementation(() => {
      return 'whatever';
    }),
}

์ œ ๊ฒฝ์šฐ์—๋Š” ๊ตฌํ˜„์„ ์Šคํ… ์•„์›ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋ฏธ๋ž˜์— ๋ˆ„๊ตฐ๊ฐ€๋ฅผ ๋•๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋‚˜๋Š” ์ตœ๊ทผ์— ๊ฐ™์€ ๋ฌธ์ œ์— ์ง๋ฉดํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ์Šค๋ ˆ๋“œ์˜ ๋Œ€ํ™”๋Š” ๋” ์ž˜ ์ดํ•ดํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉฐ ์—ฌ๊ธฐ์—์„œ ๋‚ด ์—ฐ๊ตฌ ๊ฒฐ๊ณผ๋ฅผ ์š”์•ฝํ–ˆ์Šต๋‹ˆ๋‹ค. https://medium.com/@DavideRama/mock -spy-exported-functions-within-a-single- ๋ชจ๋“ˆ-์ธ-์ œ์ŠคํŠธ-cdf2b61af642

@qwertie ์˜ ์†”๋ฃจ์…˜์—์„œ ์˜๊ฐ์„

mockGet = jest.fn()
jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  get: mockGet
}))

React.lazy ์™€ ์ž‘๋™ํ•˜๋Š” @MajorBreakfast ?

const mockLazy = jest.fn();

jest.mock('React', () => ({
    ...jest.requireActual('React'),
    lazy: mockLazy
}));

๋‚˜๋Š” ์—ฌ์ „ํžˆ ReferenceError: React is not defined ๋ฐ›์Šต๋‹ˆ๋‹ค.

๋…๋ฆฝ ๋‚ด๋ณด๋‚ด๊ธฐ ๊ธฐ๋Šฅ ๋ชจ๋“ˆ์„ ์กฐ๋กฑํ•˜๋ ค๋ฉด:
ํŒŒ์ผ์—์„œ ๊ธฐ๋ณธ ๋‚ด๋ณด๋‚ด๊ธฐ์˜ ๋ชจ๋“  ๊ฐœ๋ณ„ ๊ธฐ๋Šฅ ๋ถ€๋ถ„์„ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค.

์˜ˆ์‹œ:
dataDao.js

ํ•จ์ˆ˜ getData()
ํ•จ์ˆ˜ setData()
ํ•จ์ˆ˜ ์‚ญ์ œ ๋ฐ์ดํ„ฐ()
{getData, setData, deleteData} ๋‚ด๋ณด๋‚ด๊ธฐ

์ด์ œ ๊ธฐ๋ณธ ์ด๋ฆ„ ์ง€์ •์œผ๋กœ ํŒŒ์ผ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ jest ํ…Œ์ŠคํŠธ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

dataDao.spec.js

* '../dataDao'์—์„œ dataDao๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ;
// ๊ฐ€์ ธ์˜ฌ ๋•Œ ํ• ๋‹น๋œ ๊ธฐ๋ณธ ์ด๋ฆ„์„ ์ฐธ์กฐํ•˜๋Š” ๋ชจ๋“ˆ์„ ๊ฐ์‹œํ•ฉ๋‹ˆ๋‹ค.
jest.spyOn(dataDao, 'getData')
jest.spyOn(dataDao, 'setData')
jest.spyOn(dataDao, '๋ฐ์ดํ„ฐ ์‚ญ์ œ')

@vchinthakunta , ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋‚ด๋ณด๋‚ด๊ธฐ/๊ฐ€์ ธ์˜ค๊ธฐ ๊ตฌ๋ฌธ์˜ ์ฃผ์š” ๋ชฉ์ ์„ ์œ„๋ฐ˜ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ชจ๋“ˆ์€ ๋” ์ด์ƒ ๋‹ค์Œ์„ ํ†ตํ•ด ํŠน์ • ๋ฉ”์†Œ๋“œ ๋˜๋Š” ๋ฐ์ดํ„ฐ ํ•„๋“œ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

import { justThisThing } from 'someModule';

๋‚ด๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ๋†“์น˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

React.lazy ์™€ ์ž‘๋™ํ•˜๋Š” @MajorBreakfast ?

const mockLazy = jest.fn();

jest.mock('React', () => ({
    ...jest.requireActual('React'),
    lazy: mockLazy
}));

๋‚˜๋Š” ์—ฌ์ „ํžˆ ReferenceError: React is not defined ๋ฐ›์Šต๋‹ˆ๋‹ค.

๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— 'React'๋Š” ์—ฌ๊ธฐ์—์„œ ์†Œ๋ฌธ์ž์—ฌ์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๊นŒ?

jest.mock('react'...)

์—ฌ๊ธฐ์—์„œ ๋ณธ ๋‹ค๋ฅธ ์†”๋ฃจ์…˜๋ณด๋‹ค ๊ฐ„๋‹จํ•œ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ž‘๋™์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. require ํ•˜๊ฑฐ๋‚˜ default ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋„์šฐ๋ฏธ/๋‚ด๋น„๊ฒŒ์ด์…˜.js

export const initHeader = () => {
    // initialise header
    ...
}

...

export const anotherHelperFunction = () => {
    // do stuff
    ...
}

Navigation.js๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ตฌ์„ฑ ์š”์†Œ

import { initHeader } from '../helpers/navigation';

jest.mock('../helpers/navigation');

...

describe('Test component', () => {

    it('should reinitialise header', () => {
        const mockHeaderInit = jest.fn();
        initHeader.mockImplementation(mockHeaderInit);

        const component = mountComponent(mockProps);
        component.simulate('click');

        expect(mockHeaderInit).toBeCalled();
    }
}
mockGet = jest.fn()
jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  get: mockGet
}))
ReferenceError: mockGet is not defined

       4 | const mockGet = jest.fn();
       5 | jest.mock('./browserStorage', () => ({
       6 |   ...jest.requireActual('./browserStorage'),
    >  7 |   get: mockGet,
         |        ^
       8 | }));

jest.mock ๊ฐ€ ํ˜ธ์ด์ŠคํŠธ๋œ ๊ฒฝ์šฐ doMock ํ•˜๊ฑฐ๋‚˜

jest.mock('./browserStorage', () => ({
  ...jest.requireActual('./browserStorage'),
  get: jest.fn(),
}));

const {get: mockGet} = require('./browserStorage');

์ด๊ฒƒ์€ ์ถฉ๋ถ„ํžˆ ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. jest ๋ฌธ์„œ์— ์ด๊ฒƒ์— ๊ด€ํ•œ ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๊ทธ๋ ‡๋‹ค๋ฉด ๋™์ผํ•œ ๋ชจ๋“ˆ ๋‚ด ์—์„œ ํ•จ์ˆ˜๋ฅผ ์กฐ๋กฑํ•˜๋Š” ์†”๋ฃจ์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๋‹ค์Œ ๋ฐฉ๋ฒ•์ด ์ €์—๊ฒŒ ํšจ๊ณผ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ํŠธ๋ฆญ์€ ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚  ๋•Œ ๋ชจ์˜ ๊ธฐ๋Šฅ์„ ๋‹ค์‹œ ์žฌ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์˜ˆ์ œ๋Š” jsonwebtoken ๋ชจ๋“ˆ์˜ verify ๊ธฐ๋Šฅ์„ ๋ชจ์˜ํ•ฉ๋‹ˆ๋‹ค.

  test('perform my test', async () => {
    // save the real jwt.verify function
    const verify = jwt.verify
    // mock it.
    jwt.verify = jest.fn().mockReturnValue({ sub: 0 })
    // do the test
    ...
    // set the real function back.
    jwt.verify = verify
  })

๋‹ค์Œ ๋ฐฉ๋ฒ•์ด ์ €์—๊ฒŒ ํšจ๊ณผ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ํŠธ๋ฆญ์€ ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚  ๋•Œ ๋ชจ์˜ ๊ธฐ๋Šฅ์„ ๋‹ค์‹œ ์žฌ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์˜ˆ์ œ๋Š” jsonwebtoken ๋ชจ๋“ˆ์˜ verify ๊ธฐ๋Šฅ์„ ๋ชจ์˜ํ•ฉ๋‹ˆ๋‹ค.

  test('perform my test', async () => {
    // save the real jwt.verify function
    const verify = jwt.verify
    // mock it.
    jwt.verify = jest.fn().mockReturnValue({ sub: 0 })
    // do the test
    ...
    // set the real function back.
    jwt.verify = verify
  })

const verify ์–ด๋””์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ์–ด์จŒ๋“  ์ด๊ฒƒ์€ ์กฐ๋กฑ๋œ ํ•จ์ˆ˜๊ฐ€ ๋‚ด๋ณด๋‚ธ const ํ•จ์ˆ˜๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด _๋™์ผํ•œ ๋ชจ๋“ˆ ๋‚ด์—์„œ_ ํ•จ์ˆ˜๋ฅผ ์กฐ๋กฑํ•˜๋Š” ์†”๋ฃจ์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๋งŽ์€ ๊ฒ€์ƒ‰ ํ›„์— ์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€ ํ•จ์ˆ˜์™€ ๋ชจ์˜ ๊ฐ์ฒด๊ฐ€ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์ผ ๊ฐ์ฒด์— ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ ๊ธฐ์‚ฌ๋“ค์€ ๋ชจ๋‘ ๊ฐ™์€ ํ•ฉ์˜์— ์ด๋ฅด๋ €์Šต๋‹ˆ๋‹ค.

https://github.com/facebook/jest/issues/936#issuecomment -438975674
https://medium.com/@qjli/how -to-mock-specific-module-function-in-jest-715e39a391f4
https://luetkemj.github.io/170421/mocking-modules-in-jest

์•„๋ฌด๋„ ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์„ ์–ธ๊ธ‰ํ•˜์ง€ ์•Š์•˜๋‹ค๋Š” ์‚ฌ์‹ค์ด ๋†€๋ž์Šต๋‹ˆ๋‹ค. ์ด ์†”๋ฃจ์…˜์ด ์ฝ”๋“œ์— ์ ํ•ฉํ•˜๋‹ค๋ฉด ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๊ณ  ์›ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

_๋ชจ์˜ํ•˜๋ ค๋Š” ๋‹จ์ผ ํ•จ์ˆ˜๋ฅผ ์ž์ฒด ๋ชจ๋“ˆ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค._

์ง„์ง€ํ•˜๊ฒŒ. ๋ชจ๋“ˆ์ด ๋‹ค๋ฅธ ๋‚ด๋ถ€ ๋ถ€ํ’ˆ๊ณผ ๋ณ„๋„๋กœ ๋‚ด๋ถ€ ๋ถ€ํ’ˆ์„ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑ๋œ ๊ฒฝ์šฐ ํด๋ž˜์Šค๊ฐ€ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์„ ์œ„๋ฐ˜ํ•˜๋Š” ๊ฒƒ์ด ๊ฑฐ์˜ ํ™•์‹คํ•ฉ๋‹ˆ๋‹ค(์˜ˆ, ์‹ค์ œ ํด๋ž˜์Šค๋Š” ์•„๋‹ˆ์ง€๋งŒ ๋ชจ๋“ˆ์€ ํด๋ž˜์Šค์ฒ˜๋Ÿผ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ์€ ์ฝ”๋“œ์˜ ๋‹จ์œ„ ์ปจํ…Œ์ด๋„ˆ์ž„). ๊ทธ ๋นจํŒ์„ ๋‚˜๋ˆ„๊ณ  ๋ถ, ๋‹น์‹ ์€ ์š”๊ตฌ๋ฅผ ์กฐ๋กฑ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ์˜ ํ•จ์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ธ ์ƒํƒœ์— ์˜์กดํ•˜๋Š” ๊ฒฝ์šฐ ์—ฌ์ „ํžˆ ๋ชจ๋“ˆ์„ ์–ด๋–ป๊ฒŒ๋“  ๋ถ„ํ• ํ•˜์ง€ ์•Š๋Š” ์ข‹์€ ์ด์œ ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ๋งŽ์€ ๋‚ด๋ถ€ ์ƒํƒœ์— ์˜์กดํ•œ๋‹ค๋Š” ๋ฐ”๋กœ ๊ทธ ์‚ฌ์‹ค์€ ๋‚˜์—๊ฒŒ ๋ชจ๋“ˆ์˜ ๋ฌธ์ œ๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ณ ๋ ค๋˜์ง€ ์•Š์•˜์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์•„๋งˆ๋„ ์–ด๋–ค ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค ๋˜๋Š” DTO๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋ถ„ํ• ํ•  ์„ธ ๋ฒˆ์งธ ๋ชจ๋“ˆ์ด ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋น„๊ณต๊ฐœ๊ฐ€ ๋  ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ๊ธฐ๋Šฅ์„ ๋‚ด๋ณด๋‚ด๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ์™ธ๋ถ€ ์ฝ”๋“œ๊ฐ€ ์กฐ๋กฑ๋œ ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€๋งŒ ์ž์ฒด์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์•ผ ํ•˜๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜๋„ ํ˜ธ์ถœํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๋‚˜๋Š” ์—ฌ๊ธฐ์—์„œ ์–ด๋–ค ์ข…๋ฅ˜์˜ ์—ฐ๊ฒฐ ๋Š๊น€์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋‹ค๊ณ  ํ™•์‹ ํ•ฉ๋‹ˆ๋‹ค. ์กฐ๋กฑ๋œ ํ•จ์ˆ˜๋Š” ๊ทธ๋Œ€๋กœ ์žˆ์–ด์•ผ ํ•˜์ง€๋งŒ ๋ชจ๋“  ํ•จ์ˆ˜๋Š” ๋ฐ˜์œผ๋กœ ๋ถ„ํ• ๋˜์–ด์•ผ ํ•˜๊ณ  ๋ฐ˜์€ ์ œ๊ฑฐ๋˜์–ด ๋‹ค๋ฅธ ๋ชจ๋“ˆ๋กœ ์ด๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ์•„์ด๋””์–ด๋ฅผ ์–ป์„.

ํ…Œ์ŠคํŠธ๊ฐ€ ๋งค์šฐ ์–ด๋ ค์›Œ์ง€๋ฉด ๊ฑฐ์˜ ํ•ญ์ƒ ๋ฆฌํŒฉํ† ๋ง์ด ํ•„์š”ํ•˜๋‹ค๋Š” ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค...

ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

const tomfoolery = require('tomfoolery'); // no longer required

์ด ์Šค๋ ˆ๋“œ๋ฅผ ์ฝ๊ณ  ์ œ์•ˆ๋œ ์†”๋ฃจ์…˜์„ ํ…Œ์ŠคํŠธํ•œ ํ›„์—๋„ ์—ฌ์ „ํžˆ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์ฝ์€ ๊ฒƒ์—์„œ ์ผ๋ถ€ ์‚ฌ๋žŒ๋“ค์€์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ–ˆ์ง€๋งŒ ์–ด๋–ป๊ฒŒ ์•Œ์•„๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋ˆ„๊ตฐ๊ฐ€ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์˜ˆ์ œ์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

// a.js
export const foo = () => 'foo-' + bar()
export const bar = () => 'bar'
// a.test.js
import {
  foo,
  bar
} from './a'

describe('foo', () => {
  it('should return foo-MOCKED_BAR', () => {
    expect(foo()).toBe('foo-MOCKED_BAR')
  })

  it('should have the mocked implementation of bar', () => {
    expect(bar()).toBe('MOCKED_BAR')
  })
})

describe('bar', () => {
  it('should have the original implementation of bar', () => {
    expect(bar()).toBe('bar')
  })
})

describe('foo and bar together', () => {
  it('should have the original implementation of both', () => {
    expect(foo()).toBe('foo-bar')
  })
})

๊ฐ์‚ฌ ํ•ด์š”!

lodash.random๊ณผ ๊ฐ™์€ ๋‹จ์ผ lodash ๋ฉ”์„œ๋“œ๋งŒ ์กฐ๋กฑํ•˜๊ณ  ์‹ถ์—ˆ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“ˆ.js

const lodash = require('lodash');

module.exports = function() {
  return lodash.random();
}

test.js

const lodash = require('lodash');
const module = require('./module.js);

it('mocks lodash', () => {
    jest.spyOn(lodash, 'random').mockImplementationOnce(() => {
      return 2;
    });

    expect(module()).toEqual(2)
});

๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค :)

typescript๋กœ ์ž‘์—…ํ•˜๋Š” ์šฐ๋ฆฌ ํŒ€์—์„œ ํšจ๊ณผ๊ฐ€ ์žˆ์—ˆ๋˜ ๊ฒƒ์€ ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ๋‚ด๋ณด๋‚ด๋Š” ๋Œ€์‹  ๋‚ด๋ณด๋‚ด๋Š” const ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
์ž‘๋™ ์•ˆํ•จ:
export function doSomething(a, b) {}
์ผํ•˜๊ณ ์žˆ๋Š”:
export const doSomething = function (a, b) {}

@arbielsk ๊ฐ€ ํ•˜๋Š” ํ–ˆ๋Š”๋ฐ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค! ํ•˜์ง€๋งŒ ๋‘ ์ข…๋ฅ˜์˜ ์ˆ˜์ถœ์˜ ์ฐจ์ด์ ์ด ๋ฌด์—‡์ธ์ง€ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค ...

@dgrcode ๊ท€ํ•˜์˜ ์˜ˆ์ œ์— ๋Œ€ํ•œ ์†”๋ฃจ์…˜์„ ์ฐพ์€ ์ ์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚ด๊ฐ€ ๋งํ•  ์ˆ˜ ์žˆ๋Š” ํ•œ, ๋‹น์‹ ์ด ํ•˜๋ ค๋Š” ๊ฒƒ์€ Jest๋ฅผ ํ†ตํ•œ ์กฐ๋กฑ์œผ๋กœ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์กฐ๋กฑ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“ˆ์˜ ์™ธ๋ถ€ ๋ณด๊ธฐ๊ฐ€ ์กฐ๋กฑ๋œ ๋ฉ”์„œ๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ๋‹ค์‹œ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ๊ท€ํ•˜์˜ ์˜ˆ์ œ์—์„œ, foo ๋ฐ bar ๊ฐ™์€ ๋ชจ๋“ˆ์— ์žˆ๊ณ  ๊ทธ๋ž˜์„œ foo ์˜์˜๋ณด๊ธฐ bar ๋ฐ–์œผ๋กœ ์กฐ๋กฑ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ท€ํ•˜์˜ ์˜ต์…˜์€ ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
1) foo ๊ฐ€ bar ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ค๋„๋ก ์ฝ”๋“œ๋ฅผ ์žฌ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
2) babel-plugin-rewire ์‚ฌ์šฉ

์ œ๊ฐ€ ์˜คํ•ดํ•˜๊ณ  ์žˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ์ •์ •ํ•ด์ฃผ์„ธ์š”!

์•ฝ๊ฐ„ ๋‹ค๋ฅธ ์š”๊ตฌ ์‚ฌํ•ญ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ•จ์ˆ˜์— ๋Œ€ํ•ด ์ „์ฒด ๋ชจ๋“ˆ _except_์„ ๋ชจ์˜ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. @MajorBreakfast ์˜ ์†”๋ฃจ์…˜์„ ์‹œ์ž‘์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ์„ ์ƒ๊ฐํ•ด ๋ƒˆ์Šต๋‹ˆ๋‹ค.

jest.mock('my-module', () => ({
  ...jest.genMockFromModule('my-module'),
  myFunction: jest.requireActual('my-module').myFunction
}))

@dgrcode ๊ท€ํ•˜์˜ ์˜ˆ์ œ์— ๋Œ€ํ•œ ์†”๋ฃจ์…˜์„ ์ฐพ์€ ์ ์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚ด๊ฐ€ ๋งํ•  ์ˆ˜ ์žˆ๋Š” ํ•œ, ๋‹น์‹ ์ด ํ•˜๋ ค๋Š” ๊ฒƒ์€ Jest๋ฅผ ํ†ตํ•œ ์กฐ๋กฑ์œผ๋กœ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ๋‚˜๋Š” ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“ˆ์˜ ์™ธ๋ถ€ ๋ณด๊ธฐ๊ฐ€ ์กฐ๋กฑ๋œ ๋ฉ”์„œ๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ๋‹ค์‹œ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ๊ท€ํ•˜์˜ ์˜ˆ์ œ์—์„œ, foo ๋ฐ bar ๊ฐ™์€ ๋ชจ๋“ˆ์— ์žˆ๊ณ  ๊ทธ๋ž˜์„œ foo ์˜์˜๋ณด๊ธฐ bar ๋ฐ–์œผ๋กœ ์กฐ๋กฑ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ท€ํ•˜์˜ ์˜ต์…˜์€ ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

  1. foo ๊ฐ€ bar ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ค๋„๋ก ์ฝ”๋“œ๋ฅผ ์žฌ๊ตฌ์„ฑํ•˜์‹ญ์‹œ์˜ค.
  2. babel-plugin-rewire ์‚ฌ์šฉ

์ œ๊ฐ€ ์˜คํ•ดํ•˜๊ณ  ์žˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ์ •์ •ํ•ด์ฃผ์„ธ์š”!

์‹ค์ œ๋กœ ๋‚ด ๊ฒฝ์šฐ๊ฐ€ ๊ทธ๋žฌ๋‹ค.
๋ชจ๋“ˆ์„ ์กฐ๋กฑํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋‚ด ์ดํ•ด๊ฐ€ ์—‰๋ง์ด์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿคฆโ€โ™‚

์›๋ž˜
2๊ฐœ์˜ ํ•จ์ˆ˜๊ฐ€ ๊ฐ™์€ ๋ชจ๋“ˆ์— ์žˆ๊ณ  ์„œ๋กœ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ

foo ์ด bar ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ

function bar() {
  return 'some-result'
}

function foo(){
  return bar()  // <-- the function I want to mock 
}

bar ๋ฅผ ์ƒˆ ํŒŒ์ผ์— ๋„ฃ์Šต๋‹ˆ๋‹ค(์กฐ๋กฑํ•  ํŒŒ์ผ).

์ƒˆ ํŒŒ์ผ์—์„œ bar ๋ฉ”์„œ๋“œ๋ฅผ ์ด๋™ํ–ˆ์œผ๋ฉฐ ์ด์ œ ์œ„์˜ ๋งŽ์€ ์˜ˆ์ œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
@yoni-abtech ๋•๋ถ„์— ์ดํ•ดํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿคฃ

์ด ์ „์ฒด ์Šค๋ ˆ๋“œ๋ฅผ ์ฝ๊ณ  ๋ฐ˜๋ณตํ•ด์„œ ํ…Œ์ŠคํŠธํ•œ ํ›„ ๋‚ด๊ฐ€ ๋ณด๋Š” ๋ฐฉ์‹์—๋Š” 3๊ฐ€์ง€ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค....

์˜ต์…˜ 1 - const ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ํ•จ์ˆ˜ ์„ ์–ธ

์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ์„ ์–ธ ๋ณด๋‹ค ํ•จ์ˆ˜ ํ‘œํ˜„์‹ ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹คํ–‰ํžˆ func-style eslint ๊ทœ์น™์ด ๋„์›€์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

export const ์‚ฌ์šฉํ•˜๋ฉด _same_ ๋ชจ๋“ˆ ๋‚ด์—์„œ ๋‹ค๋ฅธ ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” spyOn ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// hello.js
export const message = () => {
  return 'Hello world';
}

export const foo = () => {
  return message();
}
// hello.test.js
import * as testModule from './hello.js';

describe('test spyon with function expressions', function () {
  afterAll(() => {
    jest.restoreAllMocks();
  });
  it('should NOT mock message in foo', function () {
    const actual = testModule.foo();

    expect(actual).toBe('Hello world');
  });

  it('should mock message in foo', function () {
    jest.spyOn(testModule, 'message').mockReturnValue('my message');

    const actual = testModule.foo();

    expect(actual).toBe('my message');
    expect(testModule.message).toHaveBeenCalledTimes(1);
  });
});

์˜ต์…˜ 2 - rewire babel ํ”Œ๋Ÿฌ๊ทธ์ธ ์‚ฌ์šฉ

ํ•จ์ˆ˜ ํ‘œํ˜„์‹์˜ ์‚ฌ์šฉ์„ ์˜๋ฌดํ™”ํ•˜์ง€ ์•Š์œผ๋ ค๋ฉด(์˜ˆ: const ) ์ด๊ฒƒ์ด ์ข‹์€ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ๋™์ผํ•œ ๋ชจ๋“ˆ์—์„œ ํ•จ์ˆ˜๋ฅผ _rewire_ (์ผ๋ช… ๋ชจ์˜)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณด์ผ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ์ƒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํ…Œ์ŠคํŠธํ•˜์ง€๋Š” ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ทธ๋“ค์˜ ๋ฌธ์„œ ์—์„œ ๋ชจ๋“ˆ ๐Ÿ‘์—์„œ ๋‚ด๋ณด๋‚ด์ง€ ์•Š์€ ๋™์ผํ•œ ๋ชจ๋“ˆ์˜ ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค. message ํ•จ์ˆ˜๋ฅผ ๋‚ด๋ณด๋‚ด์ง€ ์•Š๊ณ  ์•„๋ž˜ ์˜ˆ๋ฅผ ์ƒ์ƒํ•ด ๋ณด์„ธ์š”.

์˜ˆ์‹œ:

// hello.js
export function message() {
  return 'Hello world';
}

export function foo() {
  return message();
}
// hello.test.js
import * as testModule from './hello.js';

describe('test rewire api', function() {
  it('should NOT mock message in foo', function () {
    const actual = testModule.foo();

    expect(actual).toBe('Hello world');
  });

  it('should mock message in foo', function () {
    testModule.__RewireAPI__.__Rewire__('message', jest.fn().mockReturnValue('my message'));

    const actual = testModule.foo();

    expect(actual).toBe('my message');
    expect(testModule.message).toHaveBeenCalledTimes(1);
    testModule.__RewireAPI__.__ResetDependency__('message');
  });
});

์˜ˆ์ œ๋Š” ๋ฌธ์„œ ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

์ฐธ๊ณ : babel ๋ณ€ํ™˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์˜ต์…˜ 3 - ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๋ณ„๋„์˜ ๋ชจ๋“ˆ/ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌ

์ด ์˜ต์…˜์€ ๊ฐ€์žฅ ๋ถˆ๋ฆฌํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์ธ mock ๊ธฐ๋Šฅ์—์„œ๋Š” ๋ถ„๋ช…ํžˆ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.


์ถ”์‹ : ์ด ํŠธ๋ ˆ๋“œ๋Š” ๋งค์šฐ ๊ณ„๋ชฝ์ ์ด๊ณ  ์ข…์ข… ์žฌ๋ฏธ์žˆ์—ˆ์ง€๋งŒ, ์ด ์‹œ๋†‰์‹œ์Šค๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์ „์ฒด ์Šค๋ ˆ๋“œ๋ฅผ ์ฝ์„ ํ•„์š”์„ฑ์„ ์™„ํ™”ํ•˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. โœŒ๏ธ

@nickofthyme ๊ฐ์‚ฌ

@nickofthyme ๊ท€ํ•˜์˜ option 1 ๋Š” ๋‚ด ์•ฑ์—์„œ ์‹คํŒจํ•˜๊ณ  create react app :

 FAIL  src/hello.test.js
  test spyon with function expressions
    โœ“ should NOT mock message in foo (3ms)
    โœ• should mock message in foo (6ms)

  โ— test spyon with function expressions โ€บ should mock message in foo

    expect(received).toBe(expected) // Object.is equality

    Expected: "my message"
    Received: "Hello world"

      17 |     const actual = testModule.foo();
      18 |
    > 19 |     expect(actual).toBe("my message");
         |                    ^
      20 |     expect(testModule.message).toHaveBeenCalledTimes(1);
      21 |   });
      22 | });

      at Object.toBe (src/hello.test.js:19:20)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        1.848s
Ran all test suites related to changed files.

@danielhusar ๋‹น์‹ ์ด ๋งž๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. CRA๋กœ ์ด๊ฒƒ์„ ํ…Œ์ŠคํŠธํ–ˆ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ ์—์„œ ์ž‘๋™ํ•˜๋„๋ก ์˜ต์…˜ 1์— ์„ค๋ช…๋œ ์กฐ๋กฑ์ด yarn test:hello ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ

> yarn test:hello
yarn run v1.16.0
$ jest --config=jest.config.js -t=hello --verbose
 PASS  src/hello/hello.test.ts
  test hello
    โœ“ should NOT mock message in foo (3ms)
    โœ“ should mock message in foo (1ms)

Test Suites: 1 skipped, 1 passed, 1 of 2 total
Tests:       1 skipped, 2 passed, 3 total
Snapshots:   0 total
Time:        1.392s
Ran all test suites with tests matching "hello".
โœจ  Done in 2.36s.

๊ทธ๊ฒƒ์€ ์‚ฌ์šฉ์ž ์ •์˜ํ•˜์—ฌ ํ•„์š” jest.config.js ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ts-jest ํ•˜๊ณ  ์ „ํ™” jest --config=./jest.config.js ์ง์ ‘ํ•˜์ง€ ํ†ตํ•ด react-scripts . jest๊ฐ€ react-scripts ์— ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ์–ด๋–ป๊ฒŒ๋“  ๊ตฌ์„ฑ์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์ด ์ˆ˜์ •์€ *.css ๋ฐ *.svg ํŒŒ์ผ์— ๋Œ€ํ•œ ๋ณ€ํ™˜์„ ์ œ๊ฑฐํ•˜๋ฏ€๋กœ App.tsx ์˜ค๋ฅ˜๋ฅผ ๋ฌด์‹œํ•˜์‹ญ์‹œ์˜ค.

์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š” ํŠน์ • ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๊นŒ?
๋‚˜๋Š” ๊ฝค ํ‘œ์ค€์ ์ธ ์„ค์ •(ts ์—†์ด)์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ  ๊ทธ๊ฒƒ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๋งํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์˜ค๋Š˜ ๋ฐค์— ์กฐ๊ธˆ ๋” ์‚ดํŽด๋ณด๊ณ  ๊ทธ๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

@danielhusar ์–ด์ ฏ๋ฐค์— ์กฐ์‚ฌํ•œ ๊ฒฐ๊ณผ ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์†”๋ฃจ์…˜์„ ์–ป์„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ์€ CRA๊ฐ€ package.json#jest ์—์„œ ์žฌ์ •์˜ ํ•  ์ˆ˜ ์žˆ๋Š” jest config transformer ์ž…๋‹ˆ๋‹ค. js ๋ฐ ts ํŒŒ์ผ์€ babel-jest ์‚ฌ์šฉํ•˜์—ฌ ๋ณ€ํ™˜๋˜์ง€๋งŒ react-scripts ๋Š” .babelrc ๊ตฌ์„ฑ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๊ณ  test ์„ค์ •ํ•œ ํ™˜๊ฒฝ ์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ ์ฐจ๋‹จ ํ•ฉ๋‹ˆ๋‹ค react-scripts test ์—ฌ๊ธฐ .

๋” ๊นŠ์ด ํŒŒ๊ณ ๋“ค๊ณ  ์‹ถ์ง€๋งŒ ์ง€๊ธˆ์€ ์‹œ๊ฐ„์ด ์—†์Šต๋‹ˆ๋‹ค.

ํ  ์—ฌ์ „ํžˆ ์ž‘๋™ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐ ์–ด๋ ค์›€์„ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค(CRA๊ฐ€ ์•„๋‹Œ ๋‚ด ์‚ฌ์šฉ์ž ์ง€์ • ์„ค์ •์—์„œ).
(jest์™€ babel-jest ๋ชจ๋‘์˜ ์ตœ์‹  ๋ฒ„์ „)

์ด๊ฒƒ์€ ๋‚ด ๋†๋‹ด ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค.

module.exports = {
  name: 'my-app',
  testURL: 'http://localhost/',
  setupFiles: ['<rootDir>/setup-jest.js'],
  setupFilesAfterEnv: ['<rootDir>/setup-test.js'],
  testMatch: ['**/__tests__/**/*.test.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
  testEnvironment: 'jest-environment-jsdom-fifteen',
  snapshotSerializers: ['enzyme-to-json/serializer'],
  globals: {
    ENV: 'test',
  },
  transform: {
    '^.+\\.[t|j]sx?$': 'babel-jest',
  },
};

๊ทธ๋ฆฌ๊ณ  ๋‚ด babelrc :

{
  "presets": [
    "@babel/react",
    ["@babel/env", {
      "corejs": "3",
      "useBuiltIns": "entry",
      "loose": true,
      "exclude": [
        "es.string.split"
      ]
    }],
    "@babel/flow"
  ],
  "plugins": [
    "array-includes",
    "lodash",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-syntax-class-properties",
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread",
    "@babel/plugin-proposal-optional-chaining"
  ],
  "env": {
    "test": {
      "plugins": ["dynamic-import-node"]
    }
  }
}

์˜ต์…˜ 1๋กœ ์–ด๋ ค์›€์„ ๊ฒช๊ณ  ์žˆ๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” () => (expression) ํ•จ์ˆ˜ ๋Œ€์‹  () => { return expression } ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์—ฌ ์ž‘๋™ํ•˜๋„๋ก ์˜ต์…˜ 1์„ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค.

import * as test from './test';

export const message = () => {
    return 'Hello world';
  }

  export const foo = () => {
    return test.message();
  }

์˜ˆ์˜์ง€๋Š” ์•Š์ง€๋งŒ ์ž‘๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@nickofthyme , ์˜ต์…˜ 1 ์€ ๋‹น์‹ ์ด ๊ฐ€์ง€๊ณ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•˜๋ฉด

const foo = () => {}
export { foo }

๊ทธ๋Ÿฐ ๋‹ค์Œ ๋ถ€์„œ์ง‘๋‹ˆ๋‹ค. ์•„๋งˆ๋„ ์ƒˆ ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์„ ๋งŒ๋“ค๊ณ  ๋‚ด๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํฅ๋ฏธ๋กœ์šด ๊ด€์ฐฐ. @maletor ๊ฐ์‚ฌ

Jest๋Š” ๋ฌธ์„œ์— ๋ชจ๋“ˆ์„ ๋ถ€๋ถ„์ ์œผ๋กœ ๋ชจ์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ES import ๋ฐ Node require ๋ฌธ ๋ชจ๋‘์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
https://jestjs.io/docs/en/jest-object#jestrequireactualmodulename

@johncmunson ์ข‹์€ ์ง€์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋“ˆ ๋ชจ์˜์— ๋Œ€ํ•ด ๋ณด์—ฌ์ค€ ์ด ์˜ˆ์ œ๋Š” jest.mock _once_ ์‹คํ–‰

์œ„์˜ ์˜ˆ๋ฅผ ๋“ค๋ฉด... foo ์™€ bar ์‚ฌ์ด์— ๋ชจ๋“ˆ์„ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅด๊ฒŒ ์กฐ๋กฑํ•˜๊ณ  ์‹ถ์€์ง€ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด bar ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

export const message = (): string => {
  return 'Hello world';
}

export const foo = (): string => {
  return message();
}

export const bar = (): (() => string) => {
  return foo;
}

์‚ฌ์šฉ jest.mock ๊ฐ€์ง„ jest.requireActual ๋‚˜๋Š” ์ด๋Ÿฐ ์‹์œผ๋กœ ๋ญ”๊ฐ€๋ฅผ ๊ฐˆ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

import * as mockTestModule from './hello';

jest.mock('./hello');
const actualTestModule = jest.requireActual('./hello');

describe('test hello', function () {
  afterAll(() => {
    jest.restoreAllMocks();
  });

  // first test doesn't depend on any other method of the module so no mocks
  it('should NOT mock message in foo', function () {
    const actual = actualTestModule.foo();

    expect(actual).toBe('Hello world');
  });

  // the second I want to mock message in foo
  it('should mock message in foo', function () {
    jest.spyOn(mockTestModule, 'message').mockReturnValue('my message');
    const actual = actualTestModule.foo();

    expect(actual).toBe('my message'); // fails
    expect(mockTestModule.message).toHaveBeenCalledTimes(1); // never called
  });

  it('should mock foo in bar', function () {
    jest.spyOn(mockTestModule, 'foo').mockReturnValue('my message');
    const actual = actualTestModule.bar();

    expect(actual()).toBe('my message'); // fails
    expect(mockTestModule.message).toHaveBeenCalledTimes(1); // never called
  });
});

jest.doMock ๋”ฐ๋กœ ์กฐ๋กฑ์„ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค.


์ฝ”๋“œ๋ฅผ ๋ณด๋ ค๋ฉด ํด๋ฆญํ•˜์„ธ์š”.

```t
* import *๋ฅผ './hello'์—์„œ testModule๋กœ;

describe('ํ…Œ์ŠคํŠธ ์•ˆ๋…•ํ•˜์„ธ์š”', ํ•จ์ˆ˜() {
AfterAll(() => {
jest.restoreAllMocks();
});

it('should NOT mock message in foo', function () {
  const actual = testModule.foo();

  expect(actual).toBe('Hello world');
});

it('should mock message in foo', function () {
  jest.doMock('./hello', () => {
    // Require the original module to not be mocked...
    const originalModule = jest.requireActual('./hello');

    return {
      ...originalModule,
      message: jest.fn().mockReturnValue('my message'),
    };
  });
  const actual = testModule.foo();

  expect(actual).toBe('my message'); // fails
  expect(testModule.message).toHaveBeenCalledTimes(1); // never called
});

it('should mock foo in bar', function () {
  jest.doMock('./hello', () => {
    // Require the original module to not be mocked...
    const originalModule = jest.requireActual('./hello');

    return {
      ...originalModule,
      foo: jest.fn().mockReturnValue('my message'),
    };
  });
  const actual = testModule.bar()();

  expect(actual).toBe('my message'); // fails
  expect(testModule.foo).toHaveBeenCalledTimes(1); // never called
});

});
```

์ด ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๋ฌธ์ œ๋Š” ์‹ค์ œ ๋ชจ๋“ˆ์„ ์š”๊ตฌํ•œ ๋‹ค์Œ foo ํ˜ธ์ถœ์ด ์—ฌ์ „ํžˆ ์‹ค์ œ message ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋ชจ์˜๋Š” ํ˜ธ์ถœ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ–ˆ์œผ๋ฉด ์ข‹๊ฒ ์ง€๋งŒ ์ด ์Šค๋ ˆ๋“œ์˜ ์˜ˆ์ œ์—๋Š” ๋„์›€์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ๋ˆ„๋ฝ๋œ ๊ฒƒ์ด ์žˆ์œผ๋ฉด ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค. ๊ธฐ๊บผ์ด ์ž˜๋ชป์„ ์ธ์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์†”๋ฃจ์…˜์„ ์ฐพ๊ณ  ์žˆ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ์ ‘ํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ ๋งŽ์€ const/function์„ ๋‚ด๋ณด๋‚ด๊ณ  ํ…Œ์ŠคํŠธ ์ค‘์ธ ํŒŒ์ผ๋กœ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๋‹ค์Œ์ด ์ €์—๊ฒŒ ๋„์›€์ด ๋˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

๋‹ค์Œ์€ ์ž‘๋™ํ•˜๋ฉฐ ์•ฝ๊ฐ„ ๋” ์งง์Šต๋‹ˆ๋‹ค.

const module = require('./module');
jest.spyOn(module, 'myFn').mockImplementation(() => 'val');

Typescript์—์„œ import ๋Œ€์‹  require :

import * as module from './module';

์ด๊ฒƒ์€ ์›๋ž˜ ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๋ณต์›ํ•˜๊ณ  ๋ชจ์˜๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ•˜๋Š” ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์†”๋ฃจ์…˜์„ ์ฐพ๊ณ  ์žˆ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ์ ‘ํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ ๋งŽ์€ const/function์„ ๋‚ด๋ณด๋‚ด๊ณ  ํ…Œ์ŠคํŠธ ์ค‘์ธ ํŒŒ์ผ๋กœ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๋‹ค์Œ์ด ์ €์—๊ฒŒ ๋„์›€์ด ๋˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

๋‹ค์Œ์€ ์ž‘๋™ํ•˜๋ฉฐ ์•ฝ๊ฐ„ ๋” ์งง์Šต๋‹ˆ๋‹ค.

const module = require('./module');
jest.spyOn(module, 'myFn').mockImplementation(() => 'val');

Typescript์—์„œ import ๋Œ€์‹  require :

import * as module from './module';

์ด๊ฒƒ์€ ์›๋ž˜ ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๋ณต์›ํ•˜๊ณ  ๋ชจ์˜๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ•˜๋Š” ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์•„, ์˜ˆ, ์ด ๋ฐฉ๋ฒ•์€ ๊ฐœ์ฒด์— getter ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Test suite failed to run

    TypeError: Cannot set property useContent of #<Object> which has only a getter

์ด ๊ฒฝ์šฐ jest.mock(..) ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. :bowing_man:

๋‚ด ๋ชจ์˜๋Š” ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

import { unsubscribe } from "../lib/my-lib"
import { MyComponent } from "./index"

test("unsubscribe gets called", () => {
    const module = require("../lib/my-lib")
    jest.spyOn(
        module,
        "unsubscribe"
    ).mockImplementation(() => jest.fn())

    const { getByTestId } = render(() => <MyComponent  />)

    let button = getByTestId("trigger.some.action.button")

    fireEvent.press(button)

    expect(unsubscribe).toHaveBeenCalled()
})

๊ทธ๋ ‡๊ฒŒ ์šฐ์•„ํ•ด ๋ณด์ด์ง€๋„ ์•Š๊ณ  ์‰ฝ๊ฒŒ ํ™•์žฅ๋˜์ง€๋„ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜๋„ ์ž˜ ์ž‘๋™ํ•˜๊ณ  ์ง€๊ธˆ์€ ์ œ ๊ฒฝ์šฐ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๋‹ค๋ฅธ ๊ตฌ๋ฌธ์„ ์ œ์•ˆํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ํ›Œ๋ฅญํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค! ์ด๊ฒƒ์€ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ด๋Š” ์œ ์ผํ•œ ๊ตฌ๋ฌธ์ž…๋‹ˆ๋‹ค.

es6 ๋ชจ๋“ˆ ์ฝ”๋“œ:

export const funcA = () => {};
export const funcB = () => {
  funcA();
};

CommonJS๋กœ ๋ณ€ํ™˜ํ•œ ํ›„:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.funcB = exports.funcA = void 0;

var funcA = function funcA() {};

exports.funcA = funcA; // You can mock or add a spy  on this `funcA`

var funcB = function funcB() {
  funcA();  // This is still original `funcA`
};

exports.funcB = funcB;

์ด ์ƒํ™ฉ์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด์•ผ mocked/spied funcA ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
function funcA() {}
exports.funcA = funcA;

function funcB() {
  exports.funcA(); // Now, this `exports.funcA` is added a spy or mocked. Keep the same reference to `funcA`
}
exports.funcB = funcB;

๋˜๋Š”,

export let funcA = () => {};
export const funcB = () => {
  exports.funcA();
};

๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ:

 PASS  myModule.test.ts (9.225s)
  funcB
    โœ“ should call funcA (3ms)

-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------|---------|----------|---------|---------|-------------------
All files    |     100 |      100 |     100 |     100 |                   
 myModule.ts |     100 |      100 |     100 |     100 |                   
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.967s
  1. funcA ๋ฅผ ์กฐ๋กฑํ•˜๊ธฐ ์œ„ํ•ด rewire ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.
    ...

๊ฒŒ๋‹ค๊ฐ€ require ๊ฐ€ ์ •ํ™•ํžˆ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ๋ณด๋ ค๋ฉด https://nodejs.org/api/modules.html#modules_exports_shortcut ๋ฌธ์„œ๋ฅผ ์‚ดํŽด๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด stackoverflow ๊ฒŒ์‹œ๋ฌผ์˜ ์†”๋ฃจ์…˜์ด ์ €์—๊ฒŒ ํšจ๊ณผ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
https://stackoverflow.com/a/53402206/1217998

๊ธฐ๋ณธ์ ์œผ๋กœ ๋จผ์ € ๋ณ€ํ™˜ํ•˜๋ ค๋Š” ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ jest.fn ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

jest.mock('../../utils', () => {
  const actualUtils = jest.requireActual('../../utils');
  const originalImplementation = actualUtils.someFun;

  return {
    ...actualUtils,
    someFun: jest.fn()
      .mockImplementation(originalImplementation),
  };
});
const utils = require('../../utils');

๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด๋ ‡๊ฒŒ ์›ํ•˜๊ฑฐ๋‚˜ ์กฐ๋กฑํ•˜๋ฉด ํ‰์†Œ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

jest.spyOn(utils, 'someFun').mockReturnValueOnce(true);

์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜์—ฌ ์›๋ž˜ ๊ตฌํ˜„์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

beforeEach(() => {
    jest.clearAllMocks();
  });

์ด stackoverflow ๊ฒŒ์‹œ๋ฌผ์˜ ์†”๋ฃจ์…˜์ด ์ €์—๊ฒŒ ํšจ๊ณผ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
https://stackoverflow.com/a/53402206/1217998

๊ธฐ๋ณธ์ ์œผ๋กœ ๋จผ์ € ๋ณ€ํ™˜ํ•˜๋ ค๋Š” ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ jest.fn ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

jest.mock('../../utils', () => {
  const actualUtils = jest.requireActual('../../utils');
  const originalImplementation = actualUtils.someFun;

  return {
    ...actualUtils,
    someFun: jest.fn()
      .mockImplementation(originalImplementation),
  };
});
const utils = require('../../utils');

๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด๋ ‡๊ฒŒ ์›ํ•˜๊ฑฐ๋‚˜ ์กฐ๋กฑํ•˜๋ฉด ํ‰์†Œ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

jest.spyOn(utils, 'someFun').mockReturnValueOnce(true);

์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜์—ฌ ์›๋ž˜ ๊ตฌํ˜„์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

beforeEach(() => {
    jest.clearAllMocks();
  });

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

Jest๋Š” ๋ฌธ์„œ์— ๋ชจ๋“ˆ์„ ๋ถ€๋ถ„์ ์œผ๋กœ ๋ชจ์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ES import ๋ฐ Node require ๋ฌธ ๋ชจ๋‘์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
https://jestjs.io/docs/en/jest-object#jestrequireactualmodulename

๋ชจ์˜ ํ•จ์ˆ˜๊ฐ€ ๋ชจ๋“ˆ ๋‚ด์—์„œ ํ˜ธ์ถœ๋˜๋ฉด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋•Œ๋กœ๋Š” ์›๋ž˜ ํ•จ์ˆ˜๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ์ผ๋ถ€ ์‚ฌ์šฉ์ž ์ •์˜(์ถ”๊ฐ€) ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•จ์ˆ˜๋ฅผ ๋ชจ์˜ํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Œ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

jest.mock('./someModule', () => {
  const moduleMock = require.requireActual('./someModule');
  return {
    ...moduleMock,
    // will mock this function 
    someFunction: (args) =>
      moduleMock.someFunction({
        ...args,
        customArgument,
      }),
  };
});

์ œ ๊ฒฝ์šฐ์—๋Š” ๊ธฐ๋ณธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ž‘๋™ํ•˜๋„๋ก ๊ตฌ์„ฑ์„ ์ „๋‹ฌํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ๋‚ด๊ฐ€ ์ด๊ฒƒ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์ด๋ฏ€๋กœ ๋” ๋‚ซ๊ฑฐ๋‚˜ ๋” ์‰ฌ์šด ์•„์ด๋””์–ด๊ฐ€ ๋– ์˜ค๋ฅด๋ฉด ๊ธฐ๊บผ์ด ๋“ฃ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. :)

FWIW https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README ์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ๋‹ค์–‘ํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋ชจ์•˜์Šต๋‹ˆ๋‹ค

์ด๊ฒƒ์€ OP ์งˆ๋ฌธ/๋ฌธ์ œ์— ๋Œ€๋‹ตํ•˜์ง€ ์•Š์ง€๋งŒ ์ผ๋ถ€ ๋ฆฌํŒฉํ† ๋ง์ด ๊ด€๋ จ๋œ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค. ๋‚ด ๊ธฐ๋Šฅ์„ ๋‹ค๋ฅธ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•œ ๋‹ค์Œ ํ•ด๋‹น ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์กฐ๋กฑํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด๋ผ๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค.

// package.json
...
"scripts": {
    "test": "jest",

...
"devDependencies": {
    "@babel/preset-env": "^7.11.5",
    "jest": "^24.9.0",
...

```js
// babel.config.js

module.exports = {
์‚ฌ์ „ ์„ค์ •: [
[
'@babel/preset-env',
{
๋Œ€์ƒ: {
๋…ธ๋“œ: 'ํ˜„์žฌ',
},
},
],
],
};

```js
// module-utils.js

export const isBabaYaga = () => {
  return false
}

// module.js

import { isBabaYaga } from './module-utils'

export const isJohnWickBabaYaga = () => {
  return isBabaYaga()
}
// modules.test.js

import { isJohnWickBabaYaga } from './module';

jest.mock('./module-utils', () => ({
    isBabaYaga: jest.fn(() => true)
}))

test('John Wick is the Baba Yaga', () => {

    // when
    const isBabaYaga = isJohnWickBabaYaga()

    // then
    expect(isBabaYaga).toBeTruthy()
});
PASS  src/module.test.js
โœ“ John Wick is the Baba Yaga (4ms)

์ตœ๊ทผ์— ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ œ์•ˆ๋œ ์†”๋ฃจ์…˜ ์ค‘ ์–ด๋Š ๊ฒƒ๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. babel-plugin-rewire๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜๊ฐ€ ๋™์ผํ•œ ๋ชจ๋“ˆ ๋‚ด์˜ ๋‹ค๋ฅธ ํ•จ์ˆ˜์— ์˜ํ•ด ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ? ์ด๊ฒƒ์€ ์ •์งํ•˜๊ฒŒ ์ž‘๋™ํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” babel ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค. ๋„์›€์„ ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

์ตœ๊ทผ์— ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ œ์•ˆ๋œ ์†”๋ฃจ์…˜ ์ค‘ ์–ด๋Š ๊ฒƒ๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. babel-plugin-rewire๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜๊ฐ€ ๋™์ผํ•œ ๋ชจ๋“ˆ ๋‚ด์˜ ๋‹ค๋ฅธ ํ•จ์ˆ˜์— ์˜ํ•ด ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ? ์ด๊ฒƒ์€ ์ •์งํ•˜๊ฒŒ ์ž‘๋™ํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” babel ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค. ๋„์›€์„ ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

https://github.com/facebook/jest/issues/936#issuecomment -659597840์„ ํ™•์ธํ•˜์…จ์Šต๋‹ˆ๊นŒ? ๋™์ผํ•œ ํŒŒ์ผ์—์„œ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ์กฐ๋กฑํ•˜๋Š” ์ตœ์†Œํ•œ์˜ ์žฌํ˜„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰