Jest: Promise ํ•ด๊ฒฐ ๋Œ€๊ธฐ์—ด์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” API ์ œ๊ณต

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

๊ธฐ๋Šฅ ์„ ์š”์ฒญํ•˜๊ฑฐ๋‚˜ ๋ฒ„๊ทธ๋ฅผ ๋ณด๊ณ  ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

_Feature_, ํ•˜์ง€๋งŒ Promise ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ๊ฝค ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

ํ˜„์žฌ ํ–‰๋™์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

์™ธ๋ถ€ ๋น„๋™๊ธฐ ์ž‘์—…์— ๋Œ€ํ•œ ํ›„์† ์กฐ์น˜์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ Promise ๋ž˜ํ•‘ ๋ฐ ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๋ชจ์˜๋ฅผ ์ œ๊ณตํ•˜๊ณ  ํ…Œ์ŠคํŠธ์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ์•ฝ์†์„ ํ•ด๊ฒฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ ์š”์†Œ:

class Component extends React.Component {
  // ...
  load() {
    Promise.resolve(this.props.load())
      .then(
        result => result
          ? result
          : Promise.reject(/* ... */)
        () => Promise.reject(/* ... */)
      )
      .then(result => this.props.afterLoad(result));
  }
}

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

const load = jest.fn(() => new Promise(succeed => load.succeed = succeed));
const afterLoad = jest.fn();
const result = 'mock result';
mount(<Component load={load} afterLoad={afterLoad} />);
// ... some interaction that requires the `load`
load.succeed(result);
expect(afterLoad).toHaveBeenCalledWith(result);

expect() ๊ฐ€ ์—ฐ๊ฒฐ๋œ ์•ฝ์† ์ฒ˜๋ฆฌ๊ธฐ๋ณด๋‹ค ๋จผ์ € ํ‰๊ฐ€๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•„์š”ํ•œ ๊ฒƒ์„ ์–ป์œผ๋ ค๋ฉด ํ…Œ์ŠคํŠธ์—์„œ ๋‚ด๋ถ€ ์•ฝ์† ์ฒด์ธ์˜ ๊ธธ์ด๋ฅผ ๋ณต์ œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

return Promise.resolve(load.succeed(result))
  // length of the `.then()` chain needs to be at least as long as in the tested code
  .then(() => {})
  .then(() => expect(result).toHaveBeenCalledWith(result));

์˜ˆ์ƒ๋˜๋Š” ๋™์ž‘์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

Jest๊ฐ€ ๋ณด๋ฅ˜ ์ค‘์ธ ๋ชจ๋“  ์•ฝ์† ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” ์ผ์ข…์˜ API๋ฅผ ์ œ๊ณตํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

load.succeed(result);
jest.flushAllPromises();
expect(result).toHaveBeenCalledWith(result);

runAllTicks ๋ฐ runAllTimers ๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ํšจ๊ณผ๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.


_๋˜๋Š” ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์ผ๋ถ€ ๊ธฐ๋Šฅ์ด๋‚˜ ํŒจํ„ด์ด ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ ์—ฌ๊ธฐ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉํ–ฅ์œผ๋กœ ์•ˆ๋‚ดํ•ด ์ฃผ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. :)_

Enhancement New API proposal

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

๋„์šฐ๋ฏธ ํ•จ์ˆ˜๋Š” ์ด๋ฅผ ์•ฝ์† ์ž์ฒด๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์™„๋ฃŒ๋œ ์ฝœ๋ฐฑ์„ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์‚ฌ์šฉ์ž ์˜์—ญ์— ๋ณด๊ด€ํ•˜๊ธฐ์— ๊ฝค ๋ฌดํ•ดํ•  ๋งŒํผ ์ถฉ๋ถ„ํžˆ ์ž‘์ง€๋งŒ ๋†๋‹ด ๋Œ€์ƒ์— ์˜ฌ๋ ค ๋†“์œผ๋ฉด ๋ถˆํ‰ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์€ ๊ฒƒ์ด ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ๋งŽ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

async await๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฑฐ์˜ ์•„๋ฆ„๋‹ต์Šต๋‹ˆ๋‹ค.

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

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

๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์•ฝ์†ํ•˜๋Š” ๋™์•ˆ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ ์•ฝ์†์œผ๋กœ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์Œ์„ ๊ธฐ์–ตํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

test('my promise test', () => { //a test function returning a Promise
  return Promise.resolve(load.succeed(result))
    .then(() => {})
    .then(() => expect(result).toHaveBeenCalledWith(result));
})

ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜์—์„œ ์•ฝ์†์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด Jest๊ฐ€ ์ด๊ฒƒ์ด ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ์ž„์„ ์ธ์‹ํ•˜๊ณ  ํ•ด๊ฒฐ๋˜๊ฑฐ๋‚˜ ์‹œ๊ฐ„ ์ดˆ๊ณผ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.

@thymikee ๋ฌผ๋ก  Jest๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์š”์ ์—์„œ ์™„์ „ํžˆ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ์—์„œ .then(() => {}) ํ–‰์„ ์–ด๋–ป๊ฒŒ ๋‚จ๊ฒผ๋Š”์ง€ ์ฃผ๋ชฉํ•˜์‹ญ์‹œ์˜ค. ์‹œ์ž‘ ๊ฒŒ์‹œ๋ฌผ์—์„œ ์ด๋ฏธ ํ–ˆ๋˜ ๊ฒƒ๋ณด๋‹ค ๋ฌธ์ œ๋ฅผ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ž์„ธํžˆ ์ฝ๊ณ  ๋ฌธ์ œ๋ฅผ ๋‹ค์‹œ ์—ด๊ฑฐ๋‚˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜์‹ญ์‹œ์˜ค.

_ํ˜ผ๋™์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด OP์˜ ์ฝ”๋“œ์— return ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค._

๋น„์Šทํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์—ฌ๊ธฐ์— ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. https://github.com/pekala/test-problem-example

๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ: ์‚ฌ์šฉ์ž ์ƒํ˜ธ ์ž‘์šฉ(ํšจ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)์˜ ๊ฒฐ๊ณผ๋กœ Redux ์ €์žฅ์†Œ์— ํŒŒ๊ฒฌ๋œ ์ผ๋ จ์˜ ์ž‘์—…์„ ์ฃผ์žฅํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. Promises๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋””์ŠคํŒจ์น˜๋œ ๋™๊ธฐํ™” ๋ฐ ๋น„๋™๊ธฐ ์ž‘์—…(์ฆ‰์‹œ ํ•ด๊ฒฐ๋˜๋„๋ก ์กฐ๋กฑ). Promise ์ฒด์ธ์— ์ง์ ‘ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ Promise ์ฒด์ธ์ด ์†Œ์ง„๋œ ํ›„ ์ฃผ์žฅํ•  ๋ฐฉ๋ฒ•์ด ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. setTimeout(..., 0) ๋Š” ์ž‘๋™ํ•˜์ง€๋งŒ ํ•ดํ‚น๋œ ๊ฒƒ์ฒ˜๋Ÿผ ๋Š๊ปด์ง€๋ฉฐ setTimeout ์ฝœ๋ฐฑ์˜ ์ฃผ์žฅ์ด ์‹คํŒจํ•˜๋ฉด Jest๊ฐ€ ์‹œ๊ฐ„ ์ดˆ๊ณผ ์˜ค๋ฅ˜(์–ด์„ค์…˜ ์˜ค๋ฅ˜ ๋Œ€์‹ )์™€ ํ•จ๊ป˜ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

flushAllPromises ์˜ ์•„์ด๋””์–ด๋Š” ํ•ด๊ฒฐ์ฑ…์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ๊ทธ๊ฒƒ์ด runAllTicks ๊ฐ€ ํ•ด์•ผ ํ•  ์ผ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

ํ›„์† ์กฐ์น˜๋กœ: setTimeout(..., 0) ๋ฅผ setImmediate ๋ฐ”๊พธ๋ ค๊ณ  ์‹œ๋„ํ–ˆ๋Š”๋ฐ Promise ์ฝœ๋ฐฑ ๋งˆ์ดํฌ๋กœํƒœ์Šคํฌ ๋Œ€๊ธฐ์—ด์ด ์†Œ์ง„๋œ ํ›„ ์–ด์„ค์…˜์„ ์‹คํ–‰ํ•˜๊ณ  Jest๊ฐ€ ์–ด์„ค์…˜ ์˜ค๋ฅ˜์—์„œ ์‹œ๊ฐ„ ์ดˆ๊ณผ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๊ฒƒ์€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋ฉฐ ๋‚ด ์‚ฌ์šฉ ์‚ฌ๋ก€์— ์ ํ•ฉํ•œ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค.

test('changing the reddit downloads posts', done => {
    setImmediate(() => {
        // assertions...
        done()
    })
})

๋„์šฐ๋ฏธ ํ•จ์ˆ˜๋Š” ์ด๋ฅผ ์•ฝ์† ์ž์ฒด๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์™„๋ฃŒ๋œ ์ฝœ๋ฐฑ์„ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์‚ฌ์šฉ์ž ์˜์—ญ์— ๋ณด๊ด€ํ•˜๊ธฐ์— ๊ฝค ๋ฌดํ•ดํ•  ๋งŒํผ ์ถฉ๋ถ„ํžˆ ์ž‘์ง€๋งŒ ๋†๋‹ด ๋Œ€์ƒ์— ์˜ฌ๋ ค ๋†“์œผ๋ฉด ๋ถˆํ‰ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์€ ๊ฒƒ์ด ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ๋งŽ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

async await๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฑฐ์˜ ์•„๋ฆ„๋‹ต์Šต๋‹ˆ๋‹ค.

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

@jwbay ๊ฑฐ๊ธฐ์— ์ข‹์€ ์„คํƒ•์ด ์žˆ์–ด์š” ๐Ÿ !

์ด flushPromises ์ด ํ•œ ์ค„

@pekala one liner IMO๋Š” ๋‹ค์Œ ๋ณด๋ฅ˜ ์ค‘์ธ ์•ฝ์†์ด ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•œ ๋™์ž‘์„ ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

function foo() {  
  return new Promise((res) => {
    setTimeout(() => {
      res()
    }, 2000);
  });
}

Swizzling Promise๋Š” ์–ด๋–ป๊ณ  ์ƒˆ๋กœ์šด Promise๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•œ ๋‹ค์Œ ๋ชจ๋“  Promise๋ฅผ ํ”Œ๋Ÿฌ์‹œํ•˜๋ฉด ์ด ๋ฐฐ์—ด์˜ Promise.all์—์„œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.

@talkol ๋‹น์‹ ์ด ๊ฐ€์งœ ํƒ€์ด๋จธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•œ ๊ทธ๋Ÿด ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

@pekala ์ด ์˜ˆ์ œ์—์„œ๋Š” ์‹œ๊ฐ„์— ๋„๋‹ฌํ•œ ํ›„์—๋งŒ ์•ฝ์†์ด ํ•ด๊ฒฐ๋˜๋ฏ€๋กœ ํƒ€์ด๋จธ๋ฅผ
๋‚˜๋Š” swizzling Promise๊ฐ€ ๋†๋‹ด ๋‚ด๋ถ€ ์ž‘๋™์„ ์—‰๋ง์œผ๋กœ ๋งŒ๋“ค์ง€ ์•Š์„๊นŒ ๊ฑฑ์ •ํ•ฉ๋‹ˆ๋‹ค. ์•ฝ๊ฐ„ ํ•˜๋“œ ์ฝ”์–ด์ž…๋‹ˆ๋‹ค.

๊ฐ€์งœ ํƒ€์ด๋จธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ์™„๋ฃŒํ•˜๋Š” ๋ฐ ์‹ค์ œ 2์ดˆ ์ด์ƒ์ด ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ ์ง€์—ฐ์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ @jwbay ๊ฐ€ ์ œ์•ˆํ•œ flushPromises ๊ฐ€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋‘ ๋‹น์‹ ์ด ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ๊ฒƒ์— ๋‹ฌ๋ ค ์žˆ์Šต๋‹ˆ๋‹ค :) ๋‚ด๊ฐ€ ๋งํ•˜๋Š” ๊ฒƒ์€ ํƒ€์ด๋จธ๊ฐ€ ์•ฝ์†์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ๊ณผ ๊ด€๋ จ์ด ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

setTimeout ํ˜ธ์ถœ๊ณผ ํ˜ผํ•ฉ๋˜์–ด ํ•ด๊ฒฐ๋˜์ง€ ์•Š๋Š” ์•ฝ์†๊ณผ ๊ด€๋ จ๋œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. jest v19.0.2์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ jest v20.0.0์—์„œ๋Š” Promise๊ฐ€ ํ•ด๊ฒฐ/๊ฑฐ๋ถ€ ๊ธฐ๋Šฅ์— ๋“ค์–ด๊ฐ€์ง€ ์•Š์œผ๋ฏ€๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ๋ฌธ์ œ๋Š” _์•ฝ์† ํ•ด๊ฒฐ ๋Œ€๊ธฐ์—ด์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” API๊ฐ€ ์—†๋Š” ์ด ๋ฌธ์ œ์™€ ๊ด€๋ จ์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ๋ฌธ์ œ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋ฌธ์ œ๋ฅผ ๋ณด๊ธฐ ์‹œ์ž‘ํ•œ jest v20.0.0๋ณด๋‹ค ์ด์ „์ธ ๊ฒƒ ๊ฐ™์œผ๋ฏ€๋กœ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๊ฐ€ ๊ต๋ฅ˜ํ•˜๋Š” ์ผ๋ จ์˜์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ ์˜ฌ ์—…์— ๋Œ€ํ•œ ์šฐ๋ฆฌ์˜ ํ…Œ์ŠคํŠธ์˜ ์ผ๋ถ€์™€ ์ˆ˜ ์žˆ์—ˆ๋˜ ์œ ์ผํ•œ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค setTimeout ๋ฐ S Promise ์ฝ”๋“œ์— ์‚ฌ์šฉ์˜ ๊ฒฐ๊ตญ์„ ๋ถ€๋ฅด๋Š”์„ onUpdateFailed ์ฝœ๋ฐฑ.

  ReactTestUtils.Simulate.submit(form);
  return Promise.resolve()
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => {
      expect(onUpdateFailed).toHaveBeenCalledTimes(1);
      expect(getErrorMessage(page)).toEqual('Input is invalid.');
    });

๊ทธ๋ ‡๊ฒŒ ์˜ˆ์˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์— ์žˆ๋Š” ์–ด๋–ค ์กฐ์–ธ๋„ ๋Œ€๋‹จํžˆ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ์—์„œ ์•ฝ์†์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†๋Š” ๋˜ ๋‹ค๋ฅธ ์˜ˆ:

describe('stream from promise', () => {
  it('should wait till promise resolves', () => {
    const stream = Observable.fromPromise(Promise.resolve('foo'));
    const results = [];
    stream.subscribe(data => { results.push(data); });
    jest.runAllTimers();
    expect(results).toEqual(['foo']);
  });
});

์ด ํ…Œ์ŠคํŠธ๋Š” jest 20.0.4์—์„œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

@philwhln ์˜ ์†”๋ฃจ์…˜์€ async/await๋กœ๋„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ReactTestUtils.Simulate.submit(form);

await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();

expect(onUpdateFailed).toHaveBeenCalledTimes(1);
expect(getErrorMessage(page)).toEqual('Input is invalid.');

๋‚˜๋Š” ์•ฝ์† ๋Œ€๊ธฐ์—ด์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์‚ฌ์ด์— ์•ฝ์† ๋Œ€๊ธฐ์—ด์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” ๊ธฐ๋Šฅ๋„ ์žˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ ์•ฝ์†์„ ๋ž˜ํ•‘ํ•˜๊ธฐ ์œ„ํ•ด Promise.all์„ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ž˜ํ•‘๋œ ์•ฝ์† ์ค‘ ํ•˜๋‚˜๊ฐ€ ์‹คํŒจํ•˜๋ฉด(์ด๊ฒƒ์ด ์ œ๊ฐ€ ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—) ๋‹ค์Œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ์•ฝ์†(๊ฒฝํ•ฉ ์กฐ๊ฑด, ๋น„๊ฒฐ์ •์ )์ด ๋ฐ˜ํ™˜๋œ๋‹ค๋Š” ์˜๋ฏธ๋กœ ์ฆ‰์‹œ ์•ฝ์†์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ๋‚ด ํ…Œ์ŠคํŠธ๊ฐ€ ์˜ˆ์ธกํ•  ์ˆ˜ ์—†๊ฑฐ๋‚˜ ๋ฐ˜๋ณตํ•  ์ˆ˜ ์—†๋Š” ๊ฒฐ๊ณผ๋ฅผ ๊ฐ–๋Š” ๋ชจ๋“  ์ข…๋ฅ˜์˜ ํ˜ผ๋ž€์„ ์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์„ ์ ์ ˆํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด Promise ๋ฅผ ๋ชจ์˜ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๊ฒฐ๊ตญ์—๋Š” ๋Œ€๊ธฐ์—ด์— ์žˆ๋Š” ๋ชจ๋“  ๋งˆ์ดํฌ๋กœ ์ž‘์—…์„ ํ™•์ธํ•˜์—ฌ ๋™๊ธฐ์‹์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. promise-mock ์ด ํ•˜๋Š” ์ผ์„ ๋ฐฉํ•ดํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

process.nextTick ๋Œ€๊ธฐ์—ด์— ์žˆ๋Š” ๋งˆ์ดํฌ๋กœ ์ž‘์—…์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” API๊ฐ€ ์ด๋ฏธ ์žˆ์œผ๋ฉฐ ํ•ด๋‹น API๋Š” ์•„๋งˆ๋„ Promises( jest.runAllTicks )์™€๋„ ์ž‘๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ์•ฝ์† ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ nextTick of Yaku์— ์—ฐ๊ฒฐ๋˜์–ด nextTick ํ˜ธ์ถœ์„ ํฌ์ฐฉํ•˜๊ณ  ์กฐ๊ธฐ์— ์žฌ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์žฌ์Šค๋ฏผ ์†”๋ฃจ์…˜์„ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ๋†๋‹ด์€ ์•ฝ์† ์ž์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ด๊ฒƒ์ด ๋ฌธ์ œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
๊ฒฐ๊ตญ ๋‚˜๋Š” Yaku๋ฅผ ํ•ดํ‚นํ•˜์—ฌ ๋Œ€๊ธฐ์—ด์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” ํ”Œ๋Ÿฌ์‹œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ nextTick์„ ์‚ฌ์šฉํ•˜์—ฌ ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜์ง€๋งŒ flush๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ณด๋ฅ˜ ์ค‘์ธ ๋ชจ๋“  ์•ฝ์† ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
์ถœ์ฒ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
https://github.com/lukeapage/yaku-mock
์ •๋ฆฌํ•˜๊ณ , ysmood์— ์—ฐ๋ฝํ•˜์—ฌ ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋ฌธ์„œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ด€๋ จ์ด ์žˆ์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์›ํ•˜๋Š” ์ž‘์—…์„ ๊ฑฐ์˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ํ…Œ์ŠคํŠธ์—์„œ ์•ฝ์†์„ ๋™๊ธฐํ™”ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์†”๋ฃจ์…˜์œผ๋กœ ์ €๋ฅผ ์œ„ํ•ด ์ผํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ @jwbay ์˜ ์†”๋ฃจ์…˜์ด ๋งˆ์Œ์—

jest ๊ฐ์ฒด์™€ ์œ ์‚ฌํ•œ ๊ฒƒ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ?

await jest.nextTick();

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„

const nextTick = () => new Promise(res => process.nextTick(res));

cc @cpojer @SimenB @rogeliog

React ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งˆ์šดํŠธํ•˜๊ธฐ ์œ„ํ•ด ํšจ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚˜์—๊ฒŒ๋„ Promise๊ฐ€ ์‹คํ–‰๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์ง€๋งŒ ์•ž์„œ ์–ธ๊ธ‰ํ•œ ์ˆ˜์ • ์‚ฌํ•ญ ์ค‘ ์–ด๋Š ๊ฒƒ๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋‚ด ํ…Œ์ŠคํŠธ์—์„œ ๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - if - ํ•จ์ˆ˜๊ฐ€ await ์‚ฌ์šฉํ•˜์—ฌ Promise ๊ฐœ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ–ˆ์ง€๋งŒ ๋ถˆํ–‰ํžˆ๋„ ํ•จ์ˆ˜๋Š” Promise ๊ฐœ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ๋‚ด๊ฐ€ ์ „์—ญ Promise ๊ธฐ๋Šฅ์—์„œ ์ŠคํŒŒ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜ํ–‰ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

global.Promise = require.requireActual('promise');

it('my test', async () => {
    const spy = sinon.spy(global, 'Promise');

    wrapper.props().dispatch(functionWithPromiseCalls());

    for (let i = 0; i < spy.callCount; i += 1) {
      const promise = spy.getCall(i);
      await promise.returnValue;
    }

    expect(...)
});

๋‚˜๋Š” ์ด๊ฒƒ์— ๋Œ€ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋งŒ๋‚ฌ์Šต๋‹ˆ๋‹ค (๋ฉ‹์ง„ ๊ธฐ์ˆ ์— ๋Œ€ํ•ด

์˜ˆ๋ฅผ ๋“ค์–ด ํ•จ์ˆ˜์— ์‹œ๊ฐ„ ์ดˆ๊ณผ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์‹œ๊ฐ„ ์ดˆ๊ณผ๊ฐ€ ์ •ํ™•ํ•˜๊ฒŒ ์ ์šฉ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

      jest.useFakeTimers();
      const EXPECTED_DEFAULT_TIMEOUT_MS = 10000;

      const catchHandler = jest.fn().mockImplementationOnce(err => {
        expect(err).not.toBeNull();
        expect(err.message).toContain('timeout');
      });

      // launch the async func returning a promise
      fetchStuffWithTimeout().catch(catchHandler);

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(EXPECTED_DEFAULT_TIMEOUT_MS - 1);
      await flushPromises();

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(1);
      await flushPromises();

      expect(catchHandler).toHaveBeenCalledTimes(1); // ok, rejected precisely

์•ฝ์†์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ•ด๊ฒฐ/๊ฑฐ์ ˆ์˜ ์ •ํ™•ํ•œ ํƒ€์ด๋ฐ์„ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ฑฐ๊ธฐ์— ์•ฝ์† ํ”Œ๋Ÿฌ์‹ฑ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ ์—†์ด๋Š” ๊ธฐ๋Œ€๊ฐ€ ๋„ˆ๋ฌด ์ผ์ฐ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ๋ฌธ์ œ๋ฅผ ์ค„์ด๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

ํŒ”๋กœ์šฐํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์„ ์œ„ํ•ด ์—ฌ๊ธฐ์— ๋Œ€ํ•œ ๊ณต๊ฐœ PR์ด ์žˆ์Šต๋‹ˆ๋‹ค. #6876

https://github.com/airbnb/enzyme/issues/1587 ์—์„œ ๊ต์ฐจ ๊ฒŒ์‹œ

๋‹ค์Œ ํŒจํ„ด์ด ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•ด์•ผ ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์ œ๊ฐ€ ๋‚˜์œ ์Šต๊ด€์œผ๋กœ ๊ฐ„์ฃผ๋˜์–ด ํ•ด์„œ๋Š” ์•ˆ ๋˜๋Š” ์ผ์„ ํ•˜๊ณ  ์žˆ๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ๋žŒ๋“ค์€ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์— ๋Œ€ํ•ด ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•ฉ๋‹ˆ๊นŒ?

export class MyComponent extends React.Component {
  constructor (props) {
    super(props)

    this.hasFinishedAsync = new Promise((resolve, reject) => {
      this.finishedAsyncResolve = resolve
    })
  }

  componentDidMount () {
    this.doSomethingAsync()
  }

  async doSomethingAsync () {
    try {
      actuallyDoAsync()
      this.props.callback()
      this.finishedAsyncResolve('success')
    } catch (error) {
      this.props.callback()
      this.finishedAsyncResolve('error')
    }
  }

  // the rest of the component
}

๊ทธ๋ฆฌ๊ณ  ํ…Œ์ŠคํŠธ์—์„œ:

it(`should properly await for async code to finish`, () => {
  const mockCallback = jest.fn()
  const wrapper = shallow(<MyComponent callback={mockCallback}/>)

  expect(mockCallback.mock.calls.length).toBe(0)

  await wrapper.instance().hasFinishedAsync

  expect(mockCallback.mock.calls.length).toBe(1)
})

componentDidMount์—์„œ ๋น„๋™๊ธฐ ํ˜ธ์ถœ์ด ์ง์ ‘ ์ˆ˜ํ–‰๋˜์ง€ ์•Š์•˜์„ ๋•Œ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ ๋‹ค๋ฅธ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋“ฑ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๋น„๋™๊ธฐ ์ฒด์ธ์— ์ถ”๊ฐ€ ๋น„๋™๊ธฐ ๋‹จ๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ ์ถ”๊ฐ€ .then() ๋˜๋Š” ์ถ”๊ฐ€ await ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜์ง€๋งŒ ์ด๊ฒƒ์€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์ง€ ๋ง์•„์•ผ ํ•˜๋Š” ์ด์œ ๊ฐ€ ์žˆ๊ฑฐ๋‚˜ ์ด๊ฒƒ์ด ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์ข‹๊ฒŒ ๋ณด์ด๋Š” ์ด์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

๋‚˜๋Š” userland์—์„œ ์ด๊ฒƒ์„ ํ•˜๋Š” ๋ชจํ—˜์„ ํ–ˆ๊ณ  ๊ทธ๊ฒƒ์ด ์‹ค์ œ๋กœ ๊ฐ€๋Šฅํ•˜๊ณ  ๊ทธ๋ ‡๊ฒŒ ๋‚˜์˜์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ธฐ์— ์ถฉ๋ถ„ํžˆ ์ž์„ธํ•œ ๊ฒฝํ—˜ ๋ณด๊ณ ์„œ์ž…๋‹ˆ๋‹ค . TLDR์€ async / await ๋ฅผ ํ”„๋ผ๋ฏธ์Šค๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ๊ธฐ๋ณธ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ธ”๋ฃจ๋ฒ„๋“œ๋กœ, ๊ธฐ๋ณธ ํƒ€์ด๋จธ๋ฅผ ๋กœ๋ ‰์Šค๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. node_modules/ ํฌํ•จํ•œ ๋ชจ๋“  ๊ฒƒ์„ ํŠธ๋žœ์ŠคํŒŒ์ผ ; queueMicrotask ํ”„๋ผ๋ฏธ์Šค์— ํ•„์š”ํ•œ ๊ธฐ๋ณธ ์š”์†Œ์ด์ง€๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ lolex๋Š” JSDOM์ด ์ œ๊ณตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

jest.mockAllTimers() ๋ฐ Promise ์—์„œ componentDidMount() Promise ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” React ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ๋™์ผํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

#issuecomment-279171856 ์˜ ์†”๋ฃจ์…˜์€ ๋ฌธ์ œ๋ฅผ ์šฐ์•„ํ•œ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ๊ณต์‹ Jest API์—์„œ ๋น„์Šทํ•œ ๊ฒƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค!

๋‚˜๋Š” ์ตœ๊ทผ์— ๋งŽ์€ ๊ฒƒ๋“ค์„ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ๋™์•ˆ ๋ฌธ์ œ์— ๋ถ€๋”ช์ณค์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ ์•ฝ์†์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ํ•ญ์ƒ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์•˜๋˜ ๋งŽ์€ ํ…Œ์ŠคํŠธ์—์„œ ๋ฌธ์ œ๋ฅผ ๋“œ๋Ÿฌ๋ƒˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  await new Promise(resolve => setImmediate(resolve)); ์™€ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ๊ฐ€ ๊ฐ„๋‹จํ•œ ๊ฒฝ์šฐ์— ์ž‘๋™ํ–ˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ์—์„œ ๋ฐœ๊ฒฌํ–ˆ์ง€๋งŒ ํŒŒ์ดํ”„๋ฅผ ์ง€์šฐ๋ ค๋ฉด ๋ช‡ ๋ฒˆ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. @quasicomputational์ด ์—ฌ๊ธฐ ์—์„œ ํƒ์ƒ‰ promise-spy . ๊ทธ๋Ÿฌ๋‚˜ ๊ฐ€์งœ ํƒ€์ด๋จธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ํ•˜๋‚˜ ์žˆ์—ˆ๋Š”๋ฐ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค...๊ทธ๋ž˜์„œ ์•„์ง ์™„์ „ํžˆ ์ž‘๋™ํ•˜๋Š” ์†”๋ฃจ์…˜์€ ์•„๋‹™๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋˜ํ•œ ๊ทธ๋“ค์ด ์•ฝ์†์œผ๋กœ ๋ณ€ํ™˜๋˜๋Š” ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธํ•  ์ฝ”๋“œ์˜ async / await ์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•  ์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ฉ๋‹ˆ๋‹ค. ์•ฝ์†์œผ๋กœ ๋ณ€ํ™˜๋˜์ง€ ์•Š์œผ๋ฉด ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์•ฝ์†์— ์—ฐ๊ฒฐํ•˜์—ฌ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š”์ด ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Œ์„ ๋ฐœ๊ฒฌํ•˜๊ณ  ๊นจ๋‹ฌ์•˜์Šต๋‹ˆ๋‹ค.
๋ณด๋ฅ˜ ์ค‘์ธ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ํ”Œ๋Ÿฌ์‹œํ•˜์ง€ ๋ง๊ณ  ๋Œ€์‹  ๋ณด๋ฅ˜ ์ค‘์ธ ํ”„๋ผ๋ฏธ์Šค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ „์ฒด ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Abort Controller๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋œ ์ฝ”๋“œ ๋‚ด์—์„œ ๋ณด๋ฅ˜ ์ค‘์ธ ์•ฝ์†์„ ๊ฐ•์ œ๋กœ ์ค‘๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
https://developers.google.com/web/updates/2017/09/abortable-fetch
๋†๋‹ด์„ ๋˜์ง€๋Š” ์•ฝ์†์„ ๊ฐ–๋Š” ๊ฒƒ์€ "๋™์‹œ์„ฑ์€ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธํ•˜์ง€ ๋ง์ž"๋ผ๊ณ  ๋งํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ๋Š” ์ •๋ฐ˜๋Œ€์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋™์‹œ์„ฑ์€ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ๋” ๋งŽ์ด ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•˜๊ณ  ๋ณด๋ฅ˜ ์ค‘์ธ ์•ฝ์†์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ์„ ์ „ํ˜€ ํ—ˆ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด Stackoverflow ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์•ฝ์†์„ ์ค‘๋‹จํ•˜๋Š” ํ˜ผ๋ž€์„ ๊ฐ์•ˆํ•  ๋•Œ (์•„์ง) ์‰ฌ์šด ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค.
https://stackoverflow.com/a/53933849/373542
๋‚ด ๊ฐ€์ ธ์˜ค๊ธฐ ์•ฝ์†์„ ์ค‘๋‹จํ•˜๋Š” KISS ๊ตฌํ˜„์„ ์ž‘์„ฑํ•˜๋ ค๊ณ  ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์—ฌ๊ธฐ์— ๊ฒŒ์‹œํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@giorgio-zamparelli: _"๋™์‹œ์„ฑ์€ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธํ•˜์ง€ ๋ง์ž"_๋Š” ์›๋ž˜ ๋ณด๊ณ ์„œ์˜ ์š”์ง€์—์„œ ์™„์ „ํžˆ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋Š” _pending_ Promise์™€ ๊ด€๋ จ๋œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ…Œ์ŠคํŠธ์—์„œ ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด Promise _resolution_ ์ „ํŒŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์–ด๋ ต๋‹ค๋Š” ์‚ฌ์‹ค๊ณผ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

ํ™์กฐ ์•ฝ์†์ด ์งˆ๋ณ‘ ๋Œ€์‹  ์ฆ์ƒ์„ ์น˜๋ฃŒํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

Promise๋Š” ํ”Œ๋Ÿฌ์‹œํ•  ํ•„์š” ์—†์ด ํ…Œ์ŠคํŠธ์—์„œ ์ •์ƒ์ ์œผ๋กœ ํ•ด๊ฒฐ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
ํ…Œ์ŠคํŠธ์— ๋ณด๋ฅ˜ ์ค‘์ธ ํ”„๋ผ๋ฏธ์Šค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ wait from @testing-library/react ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๊ฑฐ๋‚˜ ๋ณด๋ฅ˜ ์ค‘์ธ ํ”„๋ผ๋ฏธ์Šค๊ฐ€ ํ…Œ์ŠคํŠธ ๋ฒ”์œ„์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹ˆ๋ฉด ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์‹œ์ž‘ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์กฐ๋กฑํ•˜๊ฑฐ๋‚˜ AbortController ์‚ฌ์šฉํ•˜์—ฌ React willUnmount ์ˆ˜๋ช… ์ฃผ๊ธฐ ์ด๋ฒคํŠธ์™€ ๊ฐ™์€ ๊ณณ์—์„œ ๋ณด๋ฅ˜ ์ค‘์ธ ์•ฝ์†์„ ์ค‘๋‹จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

AbortController๋Š” ๊ฑฐ์˜ ์•„๋ฌด๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ƒˆ๋กœ์šด API์ด๋ฉฐ ํ…Œ์ŠคํŠธ์—์„œ ๋งค๋‹ฌ๋ ค ์žˆ๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์•ฝ์†์— ๋Œ€ํ•œ ์ˆ˜์ • ์‚ฌํ•ญ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ํ‹€๋ ธ๋‹ค๋Š” ๊ฒƒ์„ ์ฆ๋ช… ํ•ด๋ด:
์ด ๋ฌธ์ œ์—์„œ ๋ณด๋ฅ˜ ์ค‘์ธ ๋ฌธ์ œ์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๊ณ  ๋ณด๊ณ ํ•œ ์‚ฌ๋žŒ์ด ์ด๋ฏธ AbortController ๋ฐ jest.mock์„ ์‚ฌ์šฉํ•ด ๋ณด์•˜์ง€๋งŒ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋‚ด๊ฐ€ ํ‹€๋ ธ๋‹ค๋Š” ๊ฒƒ์ด ์‰ฝ๊ฒŒ ์ฆ๋ช…๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@giorgio-zamparelli: ์•„๋งˆ๋„ ์˜คํ•ด๋Š” _"๋Œ€๊ธฐ ์ค‘์ธ ๋ชจ๋“  ์•ฝ์† ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ํ”Œ๋Ÿฌ์‹œ"_๋ผ๋Š” ๋ฌธ๊ตฌ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐ์„œ ๋น„๋กฏ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค(๊ทธ๋ ‡๋‹ค๋ฉด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค). ๋ฌธ์ œ ์„ค๋ช…์„ ์ž์„ธํžˆ ์ฝ์œผ๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด "๋Œ€๊ธฐ ์ค‘์ธ ์•ฝ์† ์ฒ˜๋ฆฌ๊ธฐ"๋ฅผ ์˜๋ฏธํ–ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋ฐ˜๋ณตํ•ด์„œ ๋ง์”€๋“œ๋ฆฌ์ž๋ฉด, ์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” _pending_ promise์— ๋Œ€ํ•ด (์–ด๋–ค ์‹์œผ๋กœ๋“ ) ์ด์•ผ๊ธฐํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ตœ์†Œํ•œ์˜ ๋ฒˆ๊ฑฐ๋กœ์›€์œผ๋กœ promise ํ•ด๊ฒฐ์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๋‹ค๋ฅธ ๋ง๋กœ ํ•˜๋ฉด, ํ”„๋ผ๋ฏธ์Šค๊ฐ€ ํ•ด๊ฒฐ๋˜๋Š” ์ง€์ ์—์„œ ๊ทธ๊ฒƒ์— ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  ํ›„์† ํšจ๊ณผ๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์ง€์ ๊นŒ์ง€ ํˆฌ๋ช…ํ•˜๊ณ  ๊ฒฐ์ •๋ก ์ ์œผ๋กœ ๋„๋‹ฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค(์ด ๊ฒฐ๊ณผ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก).

์ €๋Š” ์ตœ๊ทผ์— ์ด๋ฅผ ์œ„ํ•ด flush-microtasks ๋ฅผ ์ถœ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์ฐจ์šฉ ๊ตฌํ˜„ ์˜ ์†”๋ฃจ์…˜ @jwbay์— ๋น„ํ•ด ๋†€๋ผ ์šธ ์ •๋„๋กœ ๋ณต์žกํ•˜๋‹ค ๋ฐ˜์ž‘์šฉ์—์„œ, ์—ฌ๊ธฐ ๋˜๋Š” @thymikee์˜ ์†”๋ฃจ์…˜ ์—ฌ๊ธฐ๋ฅผ . ๋ณต์žก์„ฑ์ด ์˜๋ฏธ ์žˆ๋Š” ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š”์ง€ ์—ฌ๋ถ€๋Š” ํ™•์‹คํ•˜์ง€ ์•Š์ง€๋งŒ ์ด ์Šค๋ ˆ๋“œ์˜ ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์—์„œ ๊ณ ๋ คํ•˜์ง€ ์•Š์€ ๊ทน๋‹จ์ ์ธ ๊ฒฝ์šฐ๋ฅผ ์„ค๋ช…ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. react-testing-library ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๊ตฌํ˜„ here ์ฐธ์กฐ). ๊ทธ๋Ÿฌ๋‚˜ ๋…ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

import { flushMicroTasks } from 'flush-microtasks'

await flushMicroTasks()

@aleclarson ํ”Œ๋Ÿฌ์‹œ ๋งˆ์ดํฌ๋กœ ์ž‘์—…๊ณผ ํ”Œ๋Ÿฌ์‹œ ์•ฝ์† ์‚ฌ์ด์— ์ฐจ์ด์ ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

@ramusus flush-promises ์€ @jwbay ์˜ ์†”๋ฃจ์…˜๊ณผ ๋™์ผํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

https://github.com/kentor/flush-promises/blob/46f58770b14fb74ce1ff27da00837c7e722b9d06/index.js

RTL์€ ๋˜ํ•œ React์˜ ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค: https://github.com/testing-library/react-testing-library/blob/8db62fee6303d16e0d5c933ec1fab5841dd2109b/src/flush-microtasks.js

ํŽธ์ง‘: ํ•˜, ์ด๋ฏธ ์–ธ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ๋žŒ๋“ค์ด ๊ทธ๊ฒƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๋•Œ Jest์— ๋นŒ๋“œํ•ด์•ผ ํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฌธ์„œ์—์„œ ๋งํฌํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ์ด ๋ฌธ์ œ๋Š” ๋™๊ธฐ์ ์œผ๋กœ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” ๊ฒƒ์— ๊ด€ํ•œ ๊ฒƒ์ธ๋ฐ, ์ด๋Š” ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ ์ด์ƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค(ํŠนํžˆ async-await ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์—).

flushPromises ์†”๋ฃจ์…˜์€ ์ฆ‰์‹œ ํ•ด๊ฒฐ๋˜์ง€๋งŒ ์•„์ง ๋ณด๋ฅ˜ ์ค‘์ธ ์•ฝ์†์—๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ , ์ข‹์€ ์ง€์ ์ž…๋‹ˆ๋‹ค. pending ์•ฝ์†์„ ์–ด๋–ป๊ฒŒ๋“  ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. async_hooks ์˜๋ฆฌํ•œ ์ผ์„ ํ•  ์ˆ˜ ์žˆ์„์ง€๋„ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•„๋งˆ๋„ userland ์ฝ”๋“œ์— ์˜ํ•ด ์ƒ์„ฑ๋œ promise์™€ Jest์™€ ๊ทธ ์˜์กด์„ฑ์— ์˜ํ•ด ์ƒ์„ฑ๋œ promise๋ฅผ ๊ตฌ๋ณ„ํ•˜๋Š” ๊ฒƒ์€ ๊ณ ํ†ต์Šค๋Ÿฌ์šธ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์นด์šดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋„๋ก Promise ๊ฐœ์ฒด๋ฅผ ๋ž˜ํ•‘/๋ชจ์˜ํ•˜๋ ค๊ณ  ์‹œ๋„ํ–ˆ์ง€๋งŒ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

const _promise = window.Promise;
window.Promise = function(promiseFunction){
    // counter
    return new _promise(promiseFunction);
}

์ฃผ์š” ๋ฌธ์ œ๋Š” ์ „์—ญ Promise ์„ ์ „ํ˜€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” async ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

์ข‹์•„, ๋‚˜๋Š” ์ด๊ฒƒ ๊ณผ ๊ฐ™์€ ์ •๋ง ํ•ดํ‚น ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.

  1. ๋ชฉ๋ก์ด ์žˆ๋Š” ์ƒˆ ๋ชจ๋“ˆ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  2. ๊ทธ ๋ชฉ๋ก์— ์•ฝ์†์„ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.
  3. ํ…Œ์ŠคํŠธ์—์„œ ์•ฝ์†์„ ํ•ด๊ฒฐํ•˜๊ณ  ๋ชฉ๋ก์—์„œ ์ œ๊ฑฐํ•˜์‹ญ์‹œ์˜ค.
  4. ์ œ ๊ฒฝ์šฐ์—๋Š” ํšจ์†Œ์—์„œ wrapper.update() ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์—ฌ๊ธฐ์—์„œ ๋น„์Šทํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  5. ๋ชฉ๋ก์ด ๋น„์–ด ์žˆ์„ ๋•Œ๊นŒ์ง€ 3๋‹จ๊ณ„์™€ 4๋‹จ๊ณ„๋ฅผ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ์— ๋งž๊ฒŒ ์ฝ”๋“œ๋ฅผ ์กฐ์ •ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์•„๋‹ˆ์ง€๋งŒ ์ด๋ฏธ ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง์—์„œ ์ด ๋…ผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฒฐ๊ตญ์€ ๊ธฐ๋‹ค๋ฆด ๋ฟ์ž…๋‹ˆ๋‹ค. ยฏ\_(ใƒ„)_/ยฏ

Jest 26์—๋Š” ์ด์— ๋Œ€ํ•œ ํฅ๋ฏธ๋กœ์šด ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๊ฐ€์งœ ํƒ€์ด๋จธ๋Š” ์ด์ œ @sinon/fake-timers๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค ( jest.useFakeTimers('modern') ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ).

๋‚ด ํ…Œ์ŠคํŠธ๋กœ ์ตœ์‹  ๊ฐ€์งœ ํƒ€์ด๋จธ๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ๋ถˆํ–‰ํžˆ๋„ await new Promise(resolve => setImmediate(resolve)); ํ•ดํ‚น์ด ๋ฌด๊ธฐํ•œ ์ค‘๋‹จ๋ฉ๋‹ˆ๋‹ค. ๋‹คํ–‰์Šค๋Ÿฝ๊ฒŒ๋„ @sinon/fake-timers ์—๋Š” "์ด๋ฒคํŠธ ๋ฃจํ”„๋„ ์ค‘๋‹จํ•˜์—ฌ ์˜ˆ์•ฝ๋œ ์•ฝ์† ์ฝœ๋ฐฑ์ด ํƒ€์ด๋จธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ _์ „์—_ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”" ๋ช‡ ๊ฐ€์ง€ *Async() ๋ฉ”์„œ๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ถˆํ–‰ํžˆ๋„ Jest API๋ฅผ ํ†ตํ•ด clock ๊ฐœ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค.

Jest๊ฐ€ clock ๊ฐœ์ฒด๋ฅผ ์ œ๊ณตํ•˜๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•„๋Š” ์‚ฌ๋žŒ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ await new Promise(setImmediate); ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋™๊ธฐ๋Š” ํ™•์ธ ๊ฐ€๋Šฅํ•œ ์•ฝ์†์„ ํ”Œ๋Ÿฌ์‹œํ•˜์—ฌ ์‹œ์Šคํ…œ์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

"ํ˜„๋Œ€์ ์ธ" ๊ฐ€์งœ ํƒ€์ด๋จธ๋Š” ๊ฒ‰๋ณด๊ธฐ์— ๋ฌด์˜๋ฏธํ•˜๊ฒŒ ์‹œ๊ฐ„์„ ์ดˆ๊ณผํ•˜์—ฌ ์‹ค์ œ๋กœ ๋‹ค๋ฅธ ํƒ€์ด๋จธ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ๋–จ์–ด์ง€๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์ด๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

describe('flushing of js-queues using different timers', () => {
  beforeAll(() => {
    // It would take the failing test 5 long seconds to time out.
    jest.setTimeout(100);
  });

  it.each([
    [
      'given real timers',
      () => {
        jest.useRealTimers();
      },
    ],
    ['given no timers', () => {}],
    [
      'given "legacy" fake timers',
      () => {
        jest.useFakeTimers('legacy');
      },
    ],
    [
      // This is the the failing scenario, not working like the other timers.
      'given "modern" fake timers',
      () => {
        jest.useFakeTimers('modern');
      },
    ],
  ])(
    '%s, when using setImmediate to flush, flushes a promise without timing out',
    async (_, initializeScenarioSpecificTimers) => {
      initializeScenarioSpecificTimers();

      let promiseIsFlushed = false;

      Promise.resolve().then(() => {
        promiseIsFlushed = true;
      });

      // Flush promises
      await new Promise(setImmediate);

      expect(promiseIsFlushed).toBe(true);
    },
  );
});

์ด์ „ ํ…Œ์ŠคํŠธ๊ฐ€ ์ง€๊ธˆ์ฒ˜๋Ÿผ ์‹คํŒจํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋‚˜์—๊ฒŒ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ์ „์—ญ "setImmediate" ๋Œ€์‹  ํŒจํ‚ค์ง€ "timers"์—์„œ ๋…ธ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ "setImmediate"๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ฝ์†์„ ํ”Œ๋Ÿฌ์‹œํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์Œ์„ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค.

import { setImmediate as flushMicroTasks } from 'timers';

it('given "modern" fake timers, when using native timers to flush, flushes a promise without timing out', async () => {
  jest.useFakeTimers('modern');

  let promiseIsFlushed = false;

  Promise.resolve().then(() => {
    promiseIsFlushed = true;
  });

  // Flush micro and macro -tasks
  await new Promise(flushMicroTasks);

  expect(promiseIsFlushed).toBe(true);
});

@aleclarson ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์†”๋ฃจ์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

https://github.com/team-igniter-from-houston-inc/async-fn
https://medium.com/houston-io/how-to-unit-test-asynchronous-code-for-javascript-in-2020-41c124be2552

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Note: asyncFn(), extends jest.fn() with a way to control resolving/rejecting of a promise
const load = asyncFn();

const afterLoad = jest.fn();
const result = 'mock result';

mount(<Component load={load} afterLoad={afterLoad} />);

// ... some interaction that requires the `load`

// Note: New way to controlling when promise resolves
await load.resolve(result);

expect(afterLoad).toHaveBeenCalledWith(result);

์•ฝ์†์„ ํ”Œ๋Ÿฌ์‹œํ•˜๊ฑฐ๋‚˜ ํƒ€์ด๋จธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์•„๋ฌด๊ฒƒ๋„ ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์‹ญ์‹œ์˜ค.

@jansav ๋ฉ‹์ง€๋‹ค/+1. Fwiw๋Š” ์ง€์—ฐ ํŒจํ„ด์ด๋ผ๋Š” ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๋” ์ข‹์€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

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

๋‚ด ์‚ฌ์šฉ ์‚ฌ๋ก€:

public static resolvingPromise<T>(result: T, delay: number = 5): Promise<T> {
    return new Promise((resolve) => {
        setTimeout(
            () => {
                resolve(result);
            },
            delay
        );
    });
}

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

it("accepts delay as second parameter", async () => {
    const spy = jest.fn();
    MockMiddleware.resolvingPromise({ mock: true }, 50).then(spy);
    jest.advanceTimersByTime(49);
    expect(spy).not.toHaveBeenCalled();
    jest.advanceTimersByTime(1);
    await Promise.resolve(); // without this line, this test won't pass
    expect(spy).toHaveBeenCalled();
});
์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰