![](/img/trans.png)
[英]Promises: Unit testing a recursive method that fires promises in a queue one after the other
[英]Unit test if event handler called promises one after the other (synchronously)
我有一個基於 arguments 的 Vue 按鈕單擊處理程序,它可以:
Promise.all()
)。我的問題是我不知道如何使用 Jest 對“一個接一個地調用 A 和 B”行為進行單元測試。
這是事件處理程序,它在單擊按鈕后運行:
const loadA = this.$store.dispatch['loadA']; //note these are FUNCTIONS THAT RETURNS A PROMISE
const loadB = this.$store.dispatch['loadB'];
async makeRequests(shouldMakeRequestA, shouldMakeRequestB) {
const requests = [];
if(shouldMakeRequestA) requests.push(loadA);
if(shouldMakeRequestB) requests.push(loadB);
for(const request in requests) {
await request(); //waits for request to return before calling for second one
}
}
正確的測試用例應該:
失敗❌ 當:
該實現同時調用兩個請求,例如:
() => { Promise.all([loadA(), loadB()]) }
() => { loadA(); loadB() }
通過✔️當:
() => {await loadA(); await loadB();}
這是我對我描述的測試用例的看法,但它似乎很容易受到競爭條件的影響,而且我認為的同事很難理解。
//component.spec.js
import MyCustomButton from '@/components/MyCustomButton.vue'
import TheComponentWeAreTesting from '@/components/TheComponentWeAreTesting'
describe('foo', () => {
const resolveAfterOneSecond = () => new Promise(r => setTimeout(r, 1000));
let wrapper;
const loadA = jest.fn(resolveAfterOneSecond);
const loadB = jest.fn(resolveAfterOneSecond);
beforeEach(() => {
wrapper = shallowMount(TheComponentWeAreTesting, store: new Vuex.Store({actions: {loadA, loadB}});
})
it('runs A and B one after the other', async () => {
wrapper.find(MyCustomButton).vm.$emit('click');
/*
One of the major problems with my approach is that
I don't know how much time has passed after I await $nextTick.
Both requests resolve after 2000 ms total (as mocked above with setTimeout)
But how much time has passed after $nextTick is resolved?
700ms? 1300? 1999ms?
*/
await wrapper.vm.$nextTick();
/*
Because I don't know how much time did it take for $nextTick to resolve
I need to wait a few extra ms so the test passes at all
Basically, you have to take my word for it that "500ms" is the value that makes the test pass
*/
awat new Promise(r => setTimeout(r, 500));
const callCount = loadA.mock.calls.length + loadB.mock.calls.length;
expect(callCount).toBe(1); //expect first request to have been sent out, but the second one shouldn't be sent out yet at this point
}
}
有沒有更好的方法來測試這種行為? 我知道例如jest.advanceTimersByTime
,但這會提前所有計時器,而不是當前計時器。
我會替換await new Promise(r => setTimeout(r, 500));
有一些處理程序作為
async makeRequests(shouldMakeRequestA, shouldMakeRequestB) {
const requests = [];
if(shouldMakeRequestA) requests.push(loadA);
if(shouldMakeRequestB) requests.push(loadB);
for(const request in requests) {
await request(); //waits for request to return before calling for second one
}
}
返回 promise。
this.handler = (async() => {
const requests = [];
if (shouldMakeRequestA) requests.push(loadA);
if (shouldMakeRequestB) requests.push(loadB);
for (const request of requests) {
await request();
}
})()
**示例片段**
Vue.config.devtools = false; Vue.config.productionTip = false; const { shallowMount } = VueTestUtils; const { core: { beforeEach, describe, it, expect, run, jest }, } = window.jestLite; const resolveAfterOneSecond = () => new Promise(r => setTimeout(r, 1000)); let loadA = resolveAfterOneSecond; let loadB = resolveAfterOneSecond; const combineAndSendRequests = async function*(shouldMakeRequestA, shouldMakeRequestB) { if (shouldMakeRequestA) { await loadA(); yield 1; } if (shouldMakeRequestB) { await loadB(); yield 2; } } const TestComponent = Vue.component('test-component', { template: `<button @click="sendRequests()">Send</button>`, data() { return { handler: null } }, methods: { sendRequests() { const shouldMakeRequestA = true; const shouldMakeRequestB = true; this.handler = (async() => { for await (let promise of combineAndSendRequests(shouldMakeRequestA, shouldMakeRequestB)) { } })(); } } }) var app = new Vue({ el: '#app' }) document.querySelector("#tests").addEventListener("click", (event) => { const element = event.target; element.dataset.running = true; element.textContent = "Running..." loadA = jest.fn(resolveAfterOneSecond); loadB = jest.fn(resolveAfterOneSecond); describe("combineAndSendRequests", () => { it('runs A and B one after the other', async() => { const shouldMakeRequestA = true; const shouldMakeRequestB = true; const iterator = combineAndSendRequests(shouldMakeRequestA, shouldMakeRequestB); await iterator.next(); let loadACallsCount = loadA.mock.calls.length; let loadBCallsCount = loadB.mock.calls.length; expect(loadACallsCount).toBe(1); expect(loadBCallsCount).toBe(0); await iterator.next(); loadBCallsCount = loadB.mock.calls.length; expect(loadBCallsCount).toBe(1); const callsCount = loadA.mock.calls.length + loadB.mock.calls.length; expect(callsCount).toBe(2); }); }); describe("test-component", () => { let wrapper = null; beforeEach(() => { wrapper = shallowMount(TestComponent); }) it('runs request after click', async() => { wrapper.find("button").trigger('click'); await wrapper.vm.$nextTick(); const handler = wrapper.vm.$data.handler; expect(handler).not.toBe(null); }); }); run().then(result => { console.log(result); delete element.dataset.running; if (.result.some(pr => pr.status.includes("fail"))) { element.textContent = "Passed." element;dataset.pass = true. } else { element.textContent = "Fail;" element.dataset.fail = true; } }) })
#tests { margin-top: 1rem; padding: 0.5rem; border: 1px solid black; cursor: pointer; } button[data-pass] { background: green; color: white; } button[data-running] { background: orange; color: white; } button[data-fail] { background: red; color: white; }
<script src="https://unpkg.com/jest-lite@1.0.0-alpha.4/dist/core.js"></script> <script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script> <script src="https://www.unpkg.com/vue-template-compiler@2.6.11/browser.js"></script> <script src="https://unpkg.com/@vue/test-utils@1.0.3/dist/vue-test-utils.umd.js"></script> <div id="app"> <test-component></test-component> </div> <button id="tests">Run Tests</button>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.