使用 moxios 和 fakeTimers 测试去抖异步请求
Testing debounced asynchronous request with moxios and fakeTimers
我正在尝试在去抖动方法中测试 axios 调用,但是如果我添加假计时器,moxios.wait() 总是会超时。
如果去抖动时间设置得足够小(例如 10 毫秒),则测试可以在没有时钟的情况下进行,但这无助于测试正确的去抖动。
我已经尝试使用 Vue.nextTick 进行试验,并尝试将回调设为 it() 异步,但我似乎只能更深入地研究杂草。正确的做法是什么?
这是一个组件和一个测试,显示了问题:
import Vue from 'vue'
import { mount } from 'vue-test-utils'
import axios from 'axios'
import moxios from 'moxios'
import _ from 'lodash'
import expect from 'expect'
import sinon from 'sinon'
let Debounced = Vue.component('Debounced',
{
template: `<div><button @click.prevent="fetch"></button></div>`,
data() {
return {
data: {}
}
},
methods: {
fetch: _.debounce(async () => {
let data = await axios.post('/test', {param: 'example'})
this.data = data
}, 100)
}
}
)
describe.only ('Test axios in debounce()', () => {
let wrapper, clock
beforeEach(() => {
clock = sinon.useFakeTimers()
moxios.install()
wrapper = mount(Debounced)
})
afterEach(() => {
moxios.uninstall()
clock.restore()
})
it ('should send off a request when clicked', (done) => {
// Given we set up axios to return something
moxios.stubRequest('/test', {
status: 200,
response: []
})
// When the button is clicked
wrapper.find('button').trigger('click')
clock.tick(100)
moxios.wait(() => {
// It should have called axios with the right params
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({param: 'example'})
done()
})
})
})
关于测试超时异常: moxios.wait
依赖于 setTimeout
但我们用自定义 js 实现替换了我们的 setTimeout
,并使moxios.wait
我们应该在 wait 调用后调用 clock.tick(1)
。
moxios.wait(() => {
// It should have called axios with the right params
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({param: 'example'})
done()
});
clock.tick(1);
这将允许测试进入回调主体...但是它会再次失败,但 request
未定义。
主要问题:问题是假计时器正在同步调用所有回调(不使用宏任务事件循环)但 Promises 仍然使用事件循环。
所以你的代码在任何 Promise "then" 被执行之前就结束了。当然,您可以将测试代码作为微任务推送以访问请求和响应数据,例如(使用 await
或将其包装为 then" 回调):
wrapper.find('button').trigger('click');
// debounced function called
clock.tick(100);
// next lines will be executed after an axios "then" callback where a request will be created.
await Promise.resolve();
// axios promise created, a request is added to moxios requests
// next "then" is added to microtask event loop
console.log("First assert");
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({ param: 'example' });
// next lines will be executed after promise "then" from fetch method
await Promise.resolve();
console.log("Second assert");
expect(wrapper.vm.data.data).toEqual([]);
done();
我为数据添加了另一个断言,以表明您的代码应该 "wait" 用于 2 "then" 回调:axios internal "then" 以及您的请求创建和您的 "then" fetch
方法。
如您所见,我删除了 wait
调用,因为如果您想等待 Promises,它实际上什么都不做。
是的,这是一个肮脏的 hack,与 axios 实现本身密切相关,但我没有更好的建议,首先我只是试图解释这个问题。
我正在尝试在去抖动方法中测试 axios 调用,但是如果我添加假计时器,moxios.wait() 总是会超时。 如果去抖动时间设置得足够小(例如 10 毫秒),则测试可以在没有时钟的情况下进行,但这无助于测试正确的去抖动。 我已经尝试使用 Vue.nextTick 进行试验,并尝试将回调设为 it() 异步,但我似乎只能更深入地研究杂草。正确的做法是什么?
这是一个组件和一个测试,显示了问题:
import Vue from 'vue'
import { mount } from 'vue-test-utils'
import axios from 'axios'
import moxios from 'moxios'
import _ from 'lodash'
import expect from 'expect'
import sinon from 'sinon'
let Debounced = Vue.component('Debounced',
{
template: `<div><button @click.prevent="fetch"></button></div>`,
data() {
return {
data: {}
}
},
methods: {
fetch: _.debounce(async () => {
let data = await axios.post('/test', {param: 'example'})
this.data = data
}, 100)
}
}
)
describe.only ('Test axios in debounce()', () => {
let wrapper, clock
beforeEach(() => {
clock = sinon.useFakeTimers()
moxios.install()
wrapper = mount(Debounced)
})
afterEach(() => {
moxios.uninstall()
clock.restore()
})
it ('should send off a request when clicked', (done) => {
// Given we set up axios to return something
moxios.stubRequest('/test', {
status: 200,
response: []
})
// When the button is clicked
wrapper.find('button').trigger('click')
clock.tick(100)
moxios.wait(() => {
// It should have called axios with the right params
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({param: 'example'})
done()
})
})
})
关于测试超时异常: moxios.wait
依赖于 setTimeout
但我们用自定义 js 实现替换了我们的 setTimeout
,并使moxios.wait
我们应该在 wait 调用后调用 clock.tick(1)
。
moxios.wait(() => {
// It should have called axios with the right params
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({param: 'example'})
done()
});
clock.tick(1);
这将允许测试进入回调主体...但是它会再次失败,但 request
未定义。
主要问题:问题是假计时器正在同步调用所有回调(不使用宏任务事件循环)但 Promises 仍然使用事件循环。
所以你的代码在任何 Promise "then" 被执行之前就结束了。当然,您可以将测试代码作为微任务推送以访问请求和响应数据,例如(使用 await
或将其包装为 then" 回调):
wrapper.find('button').trigger('click');
// debounced function called
clock.tick(100);
// next lines will be executed after an axios "then" callback where a request will be created.
await Promise.resolve();
// axios promise created, a request is added to moxios requests
// next "then" is added to microtask event loop
console.log("First assert");
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({ param: 'example' });
// next lines will be executed after promise "then" from fetch method
await Promise.resolve();
console.log("Second assert");
expect(wrapper.vm.data.data).toEqual([]);
done();
我为数据添加了另一个断言,以表明您的代码应该 "wait" 用于 2 "then" 回调:axios internal "then" 以及您的请求创建和您的 "then" fetch
方法。
如您所见,我删除了 wait
调用,因为如果您想等待 Promises,它实际上什么都不做。
是的,这是一个肮脏的 hack,与 axios 实现本身密切相关,但我没有更好的建议,首先我只是试图解释这个问题。