簡體   English   中英

單元測試事件處理程序是否一個接一個地調用promise(同步)

[英]Unit test if event handler called promises one after the other (synchronously)

我有一個基於 arguments 的 Vue 按鈕單擊處理程序,它可以:

  • 打電話只是請求A
  • 打電話只是請求B
  • 調用請求 A 和 B - 但它應該一個接一個地調用它們(如果請求 A 成功返回,則調用請求 B。基本上,實現不能使用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() }
  • 通過✔️當:

    • 該實現調用loadA,等待它的promise解析,然后調用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM