æ©èœããªã¯ãšã¹ãããã°ãå ±åããŸããïŒ
_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()
ã¯ãé£éãããPromiseãã³ãã©ãŒã®åã«è©äŸ¡ãããããããã¹ãã¯å€±æããŸãã 次ã®ããã«ãå¿
èŠãªãã®ãååŸããã«ã¯ããã¹ãã§å
åŽã®Promiseãã§ãŒã³ã®é·ããè€è£œããå¿
èŠããããŸãã
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ããä¿çäžã®ãã¹ãŠã®Promiseãã³ãã©ãŒããã©ãã·ã¥ããããã®äœããã®APIãæäŸããããšãæåŸ ããŠããŸãã
load.succeed(result);
jest.flushAllPromises();
expect(result).toHaveBeenCalledWith(result);
runAllTicks
ãšrunAllTimers
ãè©ŠããŸãããå¹æã¯ãããŸããã§ããã
_ãããã¯ãæ¢åã®æ©èœããã¿ãŒã³ãäžè¶³ããŠããå Žåã¯ãããã«ãã誰ããç§ãæ£ããæ¹åã«åããŠãããããšãæãã§ããŸãïŒïŒ_
éåæãã¹ãã§ã¯ããã¹ãé¢æ°ãPromiseãšããŠè¿ãããšãã§ããããšãèŠããŠãããšããã§ãããããã®ããã次ã®ããã«æ©èœããŸãã
test('my promise test', () => { //a test function returning a Promise
return Promise.resolve(load.succeed(result))
.then(() => {})
.then(() => expect(result).toHaveBeenCalledWith(result));
})
ãã¹ãé¢æ°ããPromiseãè¿ããšãJestã¯ãããéåæãã¹ãã§ããããšãèªèãã解決ããããã¿ã€ã ã¢ãŠããããŸã§åŸ æ©ããŸãã
@thymikeeãã¡ãããç§ã¯JestãåŸ
ãããããã«å€ãè¿ããŠããŸã-ããã¯å®å
šã«ãã€ã³ãããå€ããŠããŸãã ã³ãŒãã«.then(() => {})
ãšããè¡ãæ®ããããšã«æ³šæããŠãã ããã åé ã®æçš¿ã§ãã§ã«èª¬æããããããåé¡ãããç°¡æœã«èª¬æããæ¹æ³ãããããŸããã ãããããèªãã§ãåé¡ãåéããããåé¿æ¹æ³ã説æããŠãã ããã
_æ··ä¹±ãé¿ããããã«ãOPã®ã³ãŒãã«return
ãè¿œå ããŸããã_
åæ§ã®åé¡ãçºçããããã§èª¬æããŸããïŒ https ïŒ
ã€ãŸãããŠãŒã¶ãŒã€ã³ã¿ã©ã¯ã·ã§ã³ïŒé
µçŽ ã䜿çšããŠã·ãã¥ã¬ãŒãïŒã®çµæãšããŠReduxã¹ãã¢ã«ãã£ã¹ããããããäžé£ã®ã¢ã¯ã·ã§ã³ãè¡šæããããšããŠããŸãã Promisesã䜿çšããŠãã£ã¹ããããããåæããã³éåæãšããŠã®ã¢ã¯ã·ã§ã³ïŒããã«è§£æ±ºããããã«ã¢ãã¯ãããŸãïŒã ãããã¹ãã§ãŒã³ã«çŽæ¥ã¢ã¯ã»ã¹ã§ããªãå Žåããããã¹ãã§ãŒã³ã䜿ãæããããåŸã«äž»åŒµããæ¹æ³ã¯ãªãããã§ãã setTimeout(..., 0)
æ©èœããŸãããããããŒãªæããããŸãã setTimeout
ã®ã³ãŒã«ããã¯ã§ã¢ãµãŒã·ã§ã³ã倱æãããšãJestã¯ïŒã¢ãµãŒã·ã§ã³ãšã©ãŒã§ã¯ãªãïŒã¿ã€ã ã¢ãŠããšã©ãŒã§å€±æããŸãã
flushAllPromises
ã®ã¢ã€ãã¢ã¯è§£æ±ºçã®ããã«æããŸããããããrunAllTicks
ãã¹ãããšã ãšæããŸããïŒ
ãã©ããŒã¢ãããšããŠïŒ setTimeout(..., 0)
ãsetImmediate
眮ãæããããšããŸããããããã¯Promiseã³ãŒã«ããã¯ãã€ã¯ãã¿ã¹ã¯ãã¥ãŒã䜿ãæããããåŸã«ã¢ãµãŒã·ã§ã³ãå®è¡ããJestãã¢ãµãŒã·ã§ã³ãšã©ãŒã§ã¿ã€ã ã¢ãŠãããã®ãé²ãããã§ãã ãããã£ãŠãããã¯åé¡ãªãæ©èœããç§ã®ãŠãŒã¹ã±ãŒã¹ã§ã¯èš±å®¹ã§ãããœãªã¥ãŒã·ã§ã³ã§ãã
test('changing the reddit downloads posts', done => {
setImmediate(() => {
// assertions...
done()
})
})
ãã«ããŒé¢æ°ã¯ããèªäœãpromiseã«å€ããããšãã§ããã®ã§ãå®äºããã³ãŒã«ããã¯ãåŠçããå¿ èŠã¯ãããŸããã ããã¯ååã«å°ããã®ã§ããŠãŒã¶ãŒã©ã³ãã«çœ®ããŠããã®ã¯ããªãç¡å®³ã§ããããããjestãªããžã§ã¯ãã«é 眮ãããŠããŠãæå¥ã¯ãããŸããã ãã®ãããªãã®ã¯ç§ã®ãããžã§ã¯ãã§ãã䜿ãããŸãã
function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
test('', () => {
somethingThatKicksOffPromiseChain();
return flushPromises().then(() => {
expect(...
});
})
async awaitã䜿çšãããšãã»ãšãã©ãããã«ãªããŸãã
test('', async () => {
somethingThatKicksOffPromiseChain();
await flushPromises();
expect(...
})
@jwbayããã¯ããããã«ããçŽ æµãªç ç³ã§ãð ïŒ
ãã®flushPromises
ãã¯ã³ã©ã€ããŒã§ããããšãå€æããã®ã¯äºå®ã§ããããã®1è¡ã«å°éããæ¹æ³ã¯ãŸã£ããæããã§ã¯ãããŸããã ãããã£ãŠãJestãŠãŒã¶ãŒãutilé¢æ°ãšããŠäœ¿çšã§ããããã«ãããšã¡ãªããããããšæããŸãã
@pekalaã¯ã³ã©ã€ããŒIMOã¯ã次ã®ä¿çäžã®çŽæã解決ããããŸã§åŸ æ©ããªããããå¿ èŠãªåäœãæäŸããŸããã
function foo() {
return new Promise((res) => {
setTimeout(() => {
res()
}, 2000);
});
}
Promiseãã¹ãŠã£ãºãªã³ã°ããã®ã¯ã©ãã§ããïŒæ°ããPromiseãäœæããããããããããã€ãã®é åã«è¿œå ããŠããããã®é åå šäœã§Promise.allã§åŸ æ©ãããã¹ãŠã®Promiseããã©ãã·ã¥ããŸããïŒ
@talkolããªããåœã®ã¿ã€ããŒãç§ãã¡ã«æäŸããŠããéãããããªããšæããŸãã ç§ã¯ããããã¹ãããŠããŸããã
@pekalaã¯ãæéã«éããåŸã«ã®ã¿promiseã解決ãããããããã®äŸã§ã¿ã€ããŒãåœé ããå¿
èŠã¯ãããŸããã
枊巻ããããã¹ãåè«ã®å
éšã®ä»çµã¿ãå°ç¡ãã«ããã®ã§ã¯ãªãããšå¿é
ããŠããŸããããã¯å°ãããŒãã³ã¢ã§ã
åœã®ã¿ã€ããŒã䜿çšããªãå Žåããã¹ãã¯å®éã«2ç§ä»¥äžããããŸãã ãã¹ããã©ã¯ãã£ã¹ã¯ããããã®ã¿ã€ãã®é
延ãåé€ããããšã ãšæããŸãããã®å Žåã @ jwbayã«ãã£ãŠææ¡ãããflushPromises
ããã®åœ¹å²ãæãããŸãã
ãã¹ãŠã¯ããªãããã¹ãããããšããŠãããã®ã«äŸåããŸã:)ç§ãèšã£ãŠããã®ã¯ã¿ã€ããŒãçŽæãåŸ ã€ããšãšã¯ç¡é¢ä¿ã®æžå¿µã§ãããšããããšã ãã§ã
setTimeoutåŒã³åºããšæ··ããåã£ãŠãããPromisesã解決ãããªãããšã«é¢é£ããåé¡ãçºçããŠããŸãã jest v19.0.2ã§ã¯åé¡ã¯ãããŸããããjest v20.0.0ã§ã¯ãPromiseã解決/æåŠé¢æ°ã«å ¥ãããšããªãããããã¹ãã¯å€±æããŸãã ç§ãã¡ã®åé¡ã¯ã_Promise解決ãã¥ãŒããã©ãã·ã¥ããAPIããªã_ãšãããã®åé¡ã«é¢é£ããŠããããã§ããããã®åé¡ã¯ãåé¡ãçºçãå§ããjest v20.0.0ãããåã®ãã®ã§ããããã«æããããããå®å šã«ã¯ããããŸããã
ç§ãã¡ã¯äžé£ã®äº€äºæã£ãŠããã®ã§ãããã¯ãæã
ãæ¥ã¢ãããããšç§ãã¡ã®ãã¹ãã®ããã€ãã®ããã«ã§ããŸããå¯äžã®ãœãªã¥ãŒã·ã§ã³ã§ãsetTimeout
Sããã Promise
sã®æçµçã«åŒã³åºãã³ãŒãã§äœ¿çšããŸã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.');
});
ããã»ã©ãããã§ã¯ãªãã®ã§ãããã§ã®ã¢ããã€ã¹ã¯å€§æè¿ã§ãã
ãã¹ãããpromiseãè¿ãããšãã§ããªãå¥ã®äŸïŒ
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']);
});
});
ãã®ãã¹ãã¯jest20.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ãã¥ãŒããã©ãã·ã¥ãããŠãŒãã£ãªãã£é¢æ°ã欲ãã
ãã¹ãéã§ãpromiseãã¥ãŒããã©ãã·ã¥ããé¢æ°ã欲ããã§ãã
Promise.allã䜿çšããŠè€æ°ã®promiseãã©ããããã³ãŒãããã¹ãããŠããŸãã ãããã®ã©ãããããpromiseã®1ã€ã倱æãããšïŒããããã¹ããããããïŒãpromiseã¯ããã«è¿ãããŸããã€ãŸãã次ã®ãã¹ãã®å®è¡äžã«ä»ã®PromiseïŒç«¶åç¶æ ãé決å®è«çïŒãè¿ãããããšããããŸãã
ããã¯ãç§ã®ãã¹ãã§äºæž¬äžå¯èœ/å埩å¯èœãªçµæãââããããããããçš®é¡ã®å€§æ··ä¹±ãåŒãèµ·ãããŸãã
ãããé©åã«å®è£
ããã«ã¯ã Promise
ãã¢ãã¯ããå¿
èŠããããŸããããã«ããããã¥ãŒã«å
¥ãããããã¹ãŠã®ãã€ã¯ãã¿ã¹ã¯ãæçµçã«ç¢ºèªããŠãããããåæçã«è§£æ±ºã§ããŸãã promise-mockãè¡ã£ãŠããããšã®éªéã«ãªãäœãã
process.nextTick
ãã¥ãŒã«å
¥ãããããã€ã¯ãã¿ã¹ã¯ããã©ãã·ã¥ããAPIããã§ã«ããããã®APIã¯ããããPromisesïŒ jest.runAllTicks
ïŒã§ãæ©èœããã¯ãã§ãã
ãžã£ã¹ãã³ã䜿ã£ãŠãpromiseã©ã€ãã©ãªã§ããnextTick of Yakuã«æ¥ç¶ããnextTickã®åŒã³åºãããã£ããããŠãããããæ©æã«åçã§ããããã«ãããœãªã¥ãŒã·ã§ã³ããããŸããã
ãã ããjestã¯promiseèªäœã䜿çšããããããããåé¡ã«ãªããŸãã
çµå±ãç§ã¯Yakuãåããããããããã³ã°ããŠããã¥ãŒããã©ãã·ã¥ãããã©ãã·ã¥ã¡ãœãããäœæããŸããã ããã©ã«ãã§ã¯ãnextTickã䜿çšããŠéåžžã©ããå®è¡ãããŸãããflushãåŒã³åºããšãä¿çäžã®ãã¹ãŠã®Promiseãã³ãã©ãŒãå®è¡ãããŸãã
ãœãŒã¹ã¯ããã«ãããŸãïŒ
https://github.com/lukeapage/yaku-mock
ããã¯çä»ããysmoodã«é£çµ¡ããŠåœŒããã©ãæããã確èªããããã¥ã¡ã³ããè¿œå ããããšã§è¡ãããšãã§ããŸããããã¹ãã§promiseãåæãããããã®ç°¡åãªãœãªã¥ãŒã·ã§ã³ãšããŠãããªããæãããšãã»ãŒå®è¡ããç§ã®ããã«åããŸããã
ããã«å¯Ÿããç°¡åãªåé¿çãšããŠãç§ã¯@jwbayã®ãœãªã¥ãŒã·ã§ã³ã奜ãã§ãã
jest
ãªããžã§ã¯ãã«äŒŒããã®ãè¿œå ããã®ã¯ã©ãã§ããïŒ
await jest.nextTick();
ãšããŠå®è£
const nextTick = () => new Promise(res => process.nextTick(res));
cc @cpojer @SimenB @rogeliog
Reactã³ã³ããŒãã³ããããŠã³ãããããã«é µçŽ ã䜿çšããŠããŸãã
ç§ãPromisesã®å®è¡ãæåŸ
ããé¢æ°ãæã£ãŠããŸãããåè¿°ã®ä¿®æ£ã¯ã©ããæ©èœããŸããã§ããã ãã¹ãã§ã¯ãé¢æ°ã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(...)
});
ç§ã¯ããã®ãŠãŒã¹ã±ãŒã¹ã«ééããŸããïŒçŽ æŽããããã¯ããã¯ãããããšã@jwbay ïŒ
ããšãã°ãé¢æ°ã«ã¿ã€ã ã¢ãŠãããããã¿ã€ã ã¢ãŠããæ£ç¢ºã«é©çšãããŠããããšã確èªãããšããŸãã
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
ãè¿œå ããå¿
èŠããããŸãããããã¯åé¡ãªãæ©èœããŠããŸãã
ç§ããã®ã¢ãããŒãã䜿ãã¹ãã§ã¯ãªãçç±ã¯ãããŸããããããšãããã¯äººã ã«ãšã£ãŠè¯ãããã§ããïŒ
ç§ã¯ãŠãŒã¶ãŒã©ã³ãã§ãããè¡ãåéºã«è¡ããŸããããå®éã«ã¯å®è¡å¯èœã§ããã»ã©æªãã¯ãªãããšãããããŸããïŒãã ãããããããªãå Žåã«ééããèœãšãç©Žã¯ããªããããŸãïŒã ããã¯ãïŒããŸãããã°ïŒçŽæ¥äœ¿çšããã®ã«åå詳现ãªãšã¯ã¹ããªãšã³ã¹ã¬ããŒãã§ãã TLDRã¯ã async
/ await
ãpromiseã«ãã©ã³ã¹ãã€ã«ããbluebirdã®ãã€ãã£ãpromiseãšlolexã®ãã€ãã£ãã¿ã€ããŒã亀æããŸãã node_modules/
ãå«ããã¹ãŠããã©ã³ã¹ãã€ã«ãqueueMicrotask
ã¯promiseã«å¿
èŠãªããªããã£ãã§ãããJSDOMãæäŸããªããããããã©ã«ãã§ã¯lolexã¯ãããæäŸããŸããã
ç§ã¯åãåé¡ã«ééããjest.mockAllTimers()
åŒã³åºããŠåå¿ã³ã³ããŒãã³ãPromise
ã§componentDidMount()
ã
ïŒissuecomment-279171856ã®ãœãªã¥ãŒã·ã§ã³ã¯ããšã¬ã¬ã³ããªæ¹æ³ã§åé¡ã解決ããŸããã
å ¬åŒã®JestAPIã«ãåæ§ã®ãã®ãå¿ èŠã§ãã
ç§ã¯æè¿ãããããã®ãã®ãã¢ããã°ã¬ãŒãããŠãããšãã«åé¡ã«ééããŸãããããã¯ç§ãã¡ããã€ãçŽæãçµããã®ãåŸ
ã£ãŠããªãã£ãããããã®ãã¹ãã§åé¡ãæããã«ããŸããã await new Promise(resolve => setImmediate(resolve));
ãããªã¡ãœããã¯åçŽãªã±ãŒã¹ã§ã¯æ©èœããŸãããããã¹ãã§èŠã€ããã®ã§ããã€ããã¯ãªã¢ããããã«ãããæ°åå®è¡ããå¿
èŠããããŸããã ããã¯ã @ quasicomputationalãããã§ã®èª¿æ»ã§ãã æ®å¿µãªãããçŽæãäœæããããšãã«ãããååããã«ããã®ãã€ãããã€ã¯ãªã¢ãããããç¥ãæ¹æ³ã¯ãªããšæããŸãã ã ããç§ã¯ãããè¡ãããã®å°ããªã©ã€ãã©ãªãäœæããŸãã... promise-spy ã ãã ããåœã®ã¿ã€ããŒã䜿çšãããã¹ãã1ã€ãããããã§ã¯æ©èœããŸããã§ãã...ãã®ããããŸã å®å
šã«æ©èœãããœãªã¥ãŒã·ã§ã³ã§ã¯ãããŸããã
ãŸããããããpromiseã«ãã©ã³ã¹ãã€ã«ãããŠããå Žåããã¹ã察象ã®ã³ãŒãå
ã®async
/ await
ãã¹ãŠã§ã®ã¿æ©èœããå¯èœæ§ããããšæããŸãã ããããçŽæã«ãã©ã³ã¹ãã€ã«ãããŠããªãå Žåããã®ã©ã€ãã©ãªã¯ãããã«ããã¯ããŠãããããå®äºããã®ãåŸ
ã€ããšãã§ããŸããã
ç§ã¯ãããšåãåé¡ãæ±ããŠããããšã«æ°ã¥ãã次ã®ããšã«æ°ã¥ããŸããã
ä¿çäžã®ãããã¹ããã©ãã·ã¥ããã®ã§ã¯ãªããä¿çäžã®ãããã¹ãããå Žåã¯ãã¹ãå
šäœã倱æãããå¿
èŠããããŸãã
ãã®ããã«ããŠãAbort Controllerã䜿çšããŠããã¹ããããã³ãŒãå
ã®ä¿çäžã®promiseã匷å¶çã«äžæ¢ããŸãã
https://developers.google.com/web/updates/2017/09/abortable-fetch
åè«ãèšãããšãçŽæããããšã¯ãã䞊è¡æ§ã¯é£ããã®ã§ããã¹ãããªãã§ãã ããããšèšãããšãšåãã§ãã å®éã«ã¯ãããã¯ãŸã£ããéã®ã¯ãã§ãã
䞊è¡æ§ã¯é£ããã®ã§ãããã«ãã¹ãããä¿çäžã®çŽæã§ãã¹ãã«åæ Œããªãããã«ããå¿
èŠããããŸãã
ãã®Stackoverflowã®è³ªåã§çŽæãäžæ¢ããããšã®æ··ä¹±ãèãããšãïŒãŸã ïŒç°¡åãªããšã§ã¯ãããŸããïŒ
https://stackoverflow.com/a/53933849/373542
ãã§ãããããã¹ãäžæ¢ããKISSå®è£
ãäœæããŠãçµæãããã«æçš¿ããŸãã
@ giorgio-zamparelliïŒ_ "䞊è¡æ§ã¯é£ããã®ã§ããã¹ãããªãã§ãã ãã" _ã¯å®å šã«å ã®ã¬ããŒãã®ãã€ã³ããè¶ ããŠããŸãã ãã®åé¡ã¯_pending_promiseã«é¢ä¿ããã®ã§ã¯ãªãããã¹ãã§éåæã³ãŒããä»ããŠpromise_resolution_ãäŒæãããã®ãåŸ ã€ã®ãäžå¿ èŠã«é£ãããšããäºå®ã«é¢ä¿ããŠããŸãã
çŽ æœ®ã®çŽæã¯ãç æ°ã§ã¯ãªãçç¶ãæ²»ãããšã«ãªããšæããŸãã
çŽæã¯ããã©ãã·ã¥ããå¿
èŠãªãã«ãã¹ãã§æ£åžžã«è§£æ±ºããå¿
èŠããããŸãã
ãã¹ãã«ä¿çäžã®çŽæãããå Žåã¯ãããšãã°wait
ãã@testing-library/react
wait
ã䜿çšããŠè§£æ±ºãããã®ãåŸ
ã€ããä¿çäžã®çŽæããã¹ãã®ç¯å²ã«å«ãŸããŠããªãå Žåã¯ã次ã®ãããããè¡ãå¿
èŠããããŸãããããéå§ããã³ãŒããã¢ãã¯ãããã AbortController
ã䜿çšããŠReactwillUnmountã©ã€ããµã€ã¯ã«ã€ãã³ãã®ããã«ä¿çäžã®promiseãäžæ¢ããå¿
èŠããããŸã
AbortControllerã¯ãã»ãšãã©èª°ã䜿çšããŠããªãæ°ããAPIã§ããããã¹ãã§ã®ã»ãšãã©ã®ãã³ã°ããçŽæã®ä¿®æ£ã§ãããšæããŠããŸãã
ç§ãééã£ãŠããããšã蚌æããŠãã ããïŒ
ãã®åé¡ã§ä¿çäžã®åé¡ã®åé¡ãå ±åãã誰ãããã§ã«AbortControllerãšjest.mockã䜿çšããããšããŠããŠãããã ãã§ã¯äžååã ã£ãå Žåãç§ã¯ç°¡åã«ééã£ãŠãããšèšŒæãããå¯èœæ§ããããŸãã
@ giorgio-zamparelliïŒèª€è§£ã¯ããä¿çäžã®ãã¹ãŠã®ãããã¹ãã³ãã©ãŒããã©ãã·ã¥ããããšãããã¬ãŒãºã®äœ¿çšã«èµ·å ããŠããå¯èœæ§ããããŸãïŒããã§ããã°ãç³ãèš³ãããŸããïŒã åé¡ã®èª¬æãããèªãã§ããã ããã°å¹žãã§ãããç§ã¯ãçŽæã®ä¿çäžã®ãã³ãã©ãŒããæå³ããŸããã
ãããã£ãŠãç¹°ãè¿ãã«ãªããŸãããããã§ã¯ïŒäœããã®æ¹æ³ã§ïŒ_pending_ã®çŽæã«ã€ããŠè©±ããŠããã®ã§ã¯ãªããæå°éã®æéã§çŽæã®è§£æ±ºããã©ãã·ã¥ããããšã«ã€ããŠè©±ããŠããã®ã§ãã ãŸãã¯ãèšãæãããšãpromiseã解決ãããæç¹ãããããã«é¢é£ä»ããããåŸç¶ã®ãã¹ãŠã®å¹æãåŒã³åºãããæç¹ãŸã§ãééçãã€æ±ºå®è«çã«ååŸããããšã«ã€ããŠã§ãïŒããã«ããããã®çµæããã¹ãã§ããŸãïŒã
ç§ã¯æè¿ããã®ç®çã®ããã«flush-microtasks
ããªãªãŒã¹ããŸããã ããã¯åãããã®å®è£
ã®ãœãªã¥ãŒã·ã§ã³@jwbayãããé©ãã»ã©è€éã§ããªã¢ã¯ãããããããŸãã¯@thymikeeã®ãœãªã¥ãŒã·ã§ã³ããã«ã è€éããæå³ã®ããéãããããããã©ããã¯ããããŸãããããã®ã¹ã¬ããã®ä»ã®ãœãªã¥ãŒã·ã§ã³ã§ã¯èæ
®ãããŠããªããšããžã±ãŒã¹ã説æããŠãããšæããŸãã react-testing-library
ãããã䜿çšããŠããããïŒãããåç
§ïŒããã®å®è£
ã®ã¿ã䜿çšããŸããããå
¬éããŠããŸããã
import { flushMicroTasks } from 'flush-microtasks'
await flushMicroTasks()
@ aleclarsonflush -microtasksãšflush-promisesã«éãã¯ãããŸãã
@ramusus flush-promises
ã¯@jwbayã®ãœãªã¥ãŒã·ã§ã³ãšåãã¢ãããŒãã䜿çšããŠããããã«èŠããŸãã
https://github.com/kentor/flush-promises/blob/46f58770b14fb74ce1ff27da00837c7e722b9d06/index.js
RTLã¯Reactã®ã³ãŒããã³ããŒããŸããïŒ https ïŒ
ç·šéïŒãã¡ãããã§ã«è¿°ã¹ãïŒãã€ãªãšïŒ
人ã
ãããã䜿çšã§ãããšãã«ããããJestã«çµã¿èŸŒãå¿
èŠããããã©ããããããŸãããïŒ ãã¶ãç§ãã¡ã¯ããã¥ã¡ã³ãã§ããã«ãªã³ã¯ããããšãã§ããŸããïŒ ãã®åé¡ã¯ãããããåæçã«ãã©ãã·ã¥ããããšã«é¢ãããã®ã§ãããããã¯ç§ãã¡ãããããããšãè¶
ããŠãããšæããŸãïŒç¹ã«ã async-await
ã§ã¯äžå¯èœã§ããããïŒ
flushPromises
ãœãªã¥ãŒã·ã§ã³ã¯ãããã«è§£æ±ºãããPromiseã§ã®ã¿æ©èœããŸããããŸã ä¿çäžã®Promiseã§ã¯æ©èœããŸããã
ããŒããè¯ãç¹ã ã©ããããããpending
çŽæã远跡ããããšãå¯èœãã©ããã¯ããããŸããã async_hooks
ã§äœãè³¢ãããšãã§ãããããããŸãããã確ãã§ã¯ãããŸããã ããããããŠãŒã¶ãŒã©ã³ãã³ãŒãã«ãã£ãŠäœæããããããã¹ãšãJestãšãã®äŸåé¢ä¿ã«ãã£ãŠäœæããããããã¹ãåºå¥ããããšãããšèŠçã«ãªãã§ãããã
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()
ã¡ãœãããå«ãŸããŠããŸãããããã®ã¡ãœããã¯ããã€ãã³ãã«ãŒããäžæããã¹ã±ãžã¥ãŒã«ãããpromiseã³ãŒã«ããã¯ãã¿ã€ããŒãå®è¡ããåã«å®è¡ã§ããããã«ããŸããã æ®å¿µãªãããJestAPIãä»ããŠ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ãã䜿çšããŠpromiseããã©ãã·ã¥ããããšã§ããã ãããæã£ãŠãããšã次ã®ãã¹ããããŸãã
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 nice / +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();
});
æãåèã«ãªãã³ã¡ã³ã
ãã«ããŒé¢æ°ã¯ããèªäœãpromiseã«å€ããããšãã§ããã®ã§ãå®äºããã³ãŒã«ããã¯ãåŠçããå¿ èŠã¯ãããŸããã ããã¯ååã«å°ããã®ã§ããŠãŒã¶ãŒã©ã³ãã«çœ®ããŠããã®ã¯ããªãç¡å®³ã§ããããããjestãªããžã§ã¯ãã«é 眮ãããŠããŠãæå¥ã¯ãããŸããã ãã®ãããªãã®ã¯ç§ã®ãããžã§ã¯ãã§ãã䜿ãããŸãã
async awaitã䜿çšãããšãã»ãšãã©ãããã«ãªããŸãã