使用 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 实现本身密切相关,但我没有更好的建议,首先我只是试图解释这个问题。