๊ธฐ๋ฅ ์ ์์ฒญํ๊ฑฐ๋ ๋ฒ๊ทธ๋ฅผ ๋ณด๊ณ ํ์๊ฒ ์ต๋๊น?
_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
๋ฅผ ์๋ํ์ง๋ง ํจ๊ณผ๊ฐ ์์์ต๋๋ค.
_๋๋ ์ด๋ฏธ ์กด์ฌํ๋ ์ผ๋ถ ๊ธฐ๋ฅ์ด๋ ํจํด์ด ๋๋ฝ๋ ๊ฒฝ์ฐ ์ฌ๊ธฐ ๋๊ตฐ๊ฐ๊ฐ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ผ๋ก ์๋ดํด ์ฃผ๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. :)_
๋น๋๊ธฐ ํ ์คํธ๊ฐ ์ฝ์ํ๋ ๋์ ํ ์คํธ ํจ์๋ฅผ ์ฝ์์ผ๋ก ๋ฐํํ ์ ์์ผ๋ฏ๋ก ๋ค์๊ณผ ๊ฐ์ด ์๋ํ ์ ์์์ ๊ธฐ์ตํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
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
ํจ์์
๋๋ค.
์ข์, ๋๋ ์ด๊ฒ ๊ณผ ๊ฐ์ ์ ๋ง ํดํน ๋ฐฉ๋ฒ์ ์ฐพ์์ต๋๋ค.
wrapper.update()
๋ฅผ ์คํํฉ๋๋ค. ํ์ํ ๊ฒฝ์ฐ ์ฌ๊ธฐ์์ ๋น์ทํ ์์
์ ์ํํฉ๋๋ค.ํ ์คํธ์ ๋ง๊ฒ ์ฝ๋๋ฅผ ์กฐ์ ํ๋ ๊ฒ์ ์ข์ ๋ฐฉ๋ฒ์ด ์๋์ง๋ง ์ด๋ฏธ ์๋ฒ ์ธก ๋ ๋๋ง์์ ์ด ๋ ผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ํ์ง๋ง ๊ฒฐ๊ตญ์ ๊ธฐ๋ค๋ฆด ๋ฟ์ ๋๋ค. ยฏ\_(ใ)_/ยฏ
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();
});
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
๋์ฐ๋ฏธ ํจ์๋ ์ด๋ฅผ ์ฝ์ ์์ฒด๋ก ๋ฐ๊ฟ ์ ์์ผ๋ฏ๋ก ์๋ฃ๋ ์ฝ๋ฐฑ์ ์ฒ๋ฆฌํ ํ์๊ฐ ์์ต๋๋ค. ๊ทธ๊ฒ์ ์ฌ์ฉ์ ์์ญ์ ๋ณด๊ดํ๊ธฐ์ ๊ฝค ๋ฌดํดํ ๋งํผ ์ถฉ๋ถํ ์์ง๋ง ๋๋ด ๋์์ ์ฌ๋ ค ๋์ผ๋ฉด ๋ถํํ์ง ์์ ๊ฒ์ ๋๋ค. ์ด์ ๊ฐ์ ๊ฒ์ด ๋ด ํ๋ก์ ํธ์์ ๋ง์ด ์ฌ์ฉ๋ฉ๋๋ค.
async await๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฑฐ์ ์๋ฆ๋ต์ต๋๋ค.