diff --git a/docs/en/guides/testing-async-components.md b/docs/en/guides/testing-async-components.md new file mode 100644 index 000000000..c7959b66e --- /dev/null +++ b/docs/en/guides/testing-async-components.md @@ -0,0 +1,98 @@ +# Testing Asynchronous Behavior + +To simplify testing, `vue-test-utils` applies DOM updates _synchronously_. However, there are some techniques you need to be aware of when testing a component with asynchronous behavior such as callbacks or promises. + +One of the most common asynchronous behaviors is API calls and Vuex actions. The following examples shows how to test a method that makes an API call. This example uses Jest to run the test and to mock the HTTP library `axios`. More about Jest manual mocks can be found [here](https://facebook.github.io/jest/docs/en/manual-mocks.html#content). + +The implementation of the `axios` mock looks like this: + +``` js +export default { + get: () => new Promise(resolve => { + resolve({ data: 'value' }) + }) +} +``` + +The below component makes an API call when a button is clicked, then assigns the response to `value`. + +``` html + + + +``` + +A test can be written like this: + +``` js +import { shallow } from 'vue-test-utils' +import Foo from './Foo' +jest.mock('axios') + +test('Foo', () => { + it('fetches async when a button is clicked', () => { + const wrapper = shallow(Foo) + wrapper.find('button').trigger('click') + expect(wrapper.vm.value).toEqual('value') + }) +}) +``` + +This test currently fails because the assertion is called before the promise in `fetchResults` resolves. Most unit test libraries provide a callback to let the runner know when the test is complete. Jest and Mocha both use `done`. We can use `done` in combination with `$nextTick` or `setTimeout` to ensure any promises resolve before the assertion is made. + +``` js +test('Foo', () => { + it('fetches async when a button is clicked', (done) => { + const wrapper = shallow(Foo) + wrapper.find('button').trigger('click') + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.value).toEqual('value') + done() + }) + }) +}) +``` + +The reason `$nextTick` or `setTimeout` allow the test to pass is because the microtask queue where promise callbacks are processed run before the task queue, where `$nextTick` and `setTimeout` are processed. This means by the time the `$nexTick` and `setTimeout` run, any promise callbacks on the microtask queue will have been executed. See [here](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) for a more detailed explanation. + +Another solution is to use an `async` function and the npm package `flush-promises`. `flush-promises` flushes all pending resolved promise handlers. You can `await` the call of `flushPromises` to flush pending promises and improve the readability of your test. + +The updated test looks like this: + +``` js +import { shallow } from 'vue-test-utils' +import flushPromises from 'flush-promises' +import Foo from './Foo' +jest.mock('axios') + +test('Foo', () => { + it('fetches async when a button is clicked', async () => { + const wrapper = shallow(Foo) + wrapper.find('button').trigger('click') + await flushPromises() + expect(wrapper.vm.value).toEqual('value') + }) +}) +``` + +This same technique can be applied to Vuex actions, which return a promise by default. +