使用 jest 和 avoriaz 时如何在 Vuejs 的组件中测试异步方法

Posted

技术标签:

【中文标题】使用 jest 和 avoriaz 时如何在 Vuejs 的组件中测试异步方法【英文标题】:How to test async method in Vuejs's component when using jest and avoriaz 【发布时间】:2018-01-14 11:34:54 【问题描述】:

例如,我有一个组件看起来像这样。

<template>
<div id="app">
  <button class="my-btn" @click="increment"> val </button>
</div>
</template>

<script>
export default 
  data() 
    return 
      val: 1,
    ;
  ,
  methods: 
    increment() 
      fetch('httpstat.us/200').then((response) => 
        this.val++;
      ).catch((error) => 
        console.log(error);
      );
    ,
  ,

</script>

你也可以查看this fiddle作为一个简化的演示

为了对这个组件做单元测试,我写了这样的代码。

// Jest used here
test('should work', () => 
  // wrapper was generated by using avoriaz's mount API
  // `val` was 1 when mounted.
  expect(wrapper.data().val).toBe(1);
  // trigger click event for testing
  wrapper.find('.my-btn')[0].trigger('click');
  // `val` should be 2 when the button clicked.
  // But how can I force jest to wait until `val` was set?
  expect(wrapper.data().val).toBe(2);
);

自从expectfetch 完成之前运行,它就不起作用了,但我不知道如何让笑话等到 promise 函数完成。 那么如何在这个用例中进行测试呢?

更新我尝试了 nextTick 但没有成功。

 PASS  src/Sample.spec.js
  Sample.vue
    ✓ should work (31ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.46s
Ran all test suites.
  console.error node_modules/vue/dist/vue.runtime.common.js:477
    [Vue warn]: Error in nextTick: "Error: expect(received).toBe(expected)

    Expected value to be (using ===):
      2
    Received:
      1"

  console.error node_modules/vue/dist/vue.runtime.common.js:564
     Error: expect(received).toBe(expected)

    Expected value to be (using ===):
      2
    Received:
      1
        at /path/to/sample-project/src/Sample.spec.js:16:30
        at Array.<anonymous> (/path/to/sample-project/node_modules/vue/dist/vue.runtime.common.js:699:14)
        at nextTickHandler (/path/to/sample-project/node_modules/vue/dist/vue.runtime.common.js:646:16)
        at <anonymous>
        at process._tickCallback (internal/process/next_tick.js:188:7)
      matcherResult: 
        actual: 1,
         expected: 2,
         message: [Function],
         name: 'toBe',
         pass: false  

这是完整的测试代码。

import  mount  from 'avoriaz';
import Sample from './Sample.vue';
import Vue from 'vue';

/* eslint-disable */

describe('Sample.vue', () => 

  test('should work', () => 
    const wrapper = mount(Sample);
    // console.log(wrapper.data().val);
    expect(wrapper.data().val).toBe(1);
    wrapper.find('.my-btn')[0].trigger('click');
    Vue.nextTick(() => 
        // console.log(wrapper.data().val);
        expect(wrapper.vm.val).toBe(2);
        done();
    )
  );

)

更新 2 我添加了done,但它仍然不起作用。这是错误消息。

  console.error node_modules/vue/dist/vue.runtime.common.js:477
    [Vue warn]: Error in nextTick: "Error: expect(received).toBe(expected)

    Expected value to be (using ===):
      2
    Received:
      1"

  console.error node_modules/vue/dist/vue.runtime.common.js:564
     Error: expect(received).toBe(expected)

    Expected value to be (using ===):
      2
    Received:
      1
        at /path/to/sample-project/src/Sample.spec.js:16:30
        at Array.<anonymous> (/path/to/sample-project/node_modules/vue/dist/vue.runtime.common.js:699:14)
        at nextTickHandler (/path/to/sample-project/node_modules/vue/dist/vue.runtime.common.js:646:16)
        at <anonymous>
        at process._tickCallback (internal/process/next_tick.js:188:7)
      matcherResult: 
        actual: 1,
         expected: 2,
         message: [Function],
         name: 'toBe',
         pass: false  

 FAIL  src/Sample.spec.js (7.262s)
  ● Sample.vue › should work

    Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

      at pTimeout (node_modules/jest-jasmine2/build/queueRunner.js:53:21)
      at Timeout.callback [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:523:19)
      at ontimeout (timers.js:469:11)
      at tryOnTimeout (timers.js:304:5)
      at Timer.listOnTimeout (timers.js:264:5)

  Sample.vue
    ✕ should work (5032ms)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        8.416s
Ran all test suites.

【问题讨论】:

【参考方案1】:

小故事:从flush-promises (npm i -D flush-promises) 获得帮助,并在您的Vue.nextTick 之前致电await flushPromises()

长话短说,我有一个 EmailVerification.vue 组件和一个 submit 方法,如下所示:

    submit () 
    this.successMessage = ''
    this.verifyError = null
    this.isVerifying = true

    this.verifyEmail( 'key': this.key )
      .then(() => 
        this.isVerifying = false
        this.successMessage = 'Email verified, great! Thanks a lot. &nbsp; <i class="fa fa-home"></i><a href="/">Home</a>'
      )
      .catch((error) => 
        this.isVerifying = false
        this.verifyError = error
      )
  

这是我通过的完整测试:

const localVue = createLocalVue()
localVue.use(Vuex)

const $route = 
  path: '/verify-email/:key',
  params:  key: 'dummy_key' ,
  query:  email: 'dummy@test.com' 


describe('EmailVerification.vue', () => 
  describe('Methods', () => 
    let localAuth =  namespaced: true 
    let store
    let wrapper

    beforeEach(() => 
      localAuth.actions = 
        verifyEmail: jest.fn().mockReturnValueOnce()
      

      store = new Vuex.Store(
        namespaced: true,
        modules:  auth: localAuth ,
        strict: true
      )

      wrapper = shallowMount(EmailVerification, 
        localVue,
        store,
        stubs: 
          RouterLink: RouterLinkStub
        ,
        mocks: 
          $route
        
      )
    )

    it('should trigger verifyEmail when button is clicked', async (done) => 
      const data =  key: 'another_dummy_key' 
      wrapper.setData(data)
      const button = wrapper.find('button')

      expect(wrapper.vm.isVerifying).toBeFalsy()
      expect(wrapper.vm.verifyError).toBeNull()
      expect(button.attributes().disabled).toBeUndefined()

      button.trigger('click')

      expect(localAuth.actions.verifyEmail).toHaveBeenCalled()
      expect(localAuth.actions.verifyEmail.mock.calls[0][1]).toEqual(data)

      expect(button.attributes().disabled).toBeDefined()
      expect(wrapper.vm.isVerifying).toBeTruthy()
      expect(wrapper.vm.verifyError).toBeNull()

      const inputArray = wrapper.findAll('input')
      expect(inputArray.at(0).attributes().disabled).toBeDefined()
      expect(inputArray.at(1).attributes().disabled).toBeDefined()

      await flushPromises()

      wrapper.vm.$nextTick(() => 
        expect(wrapper.vm.isVerifying).toBeFalsy()
        expect(wrapper.vm.verifyError).toBeNull()
        done()
      )
    )
  )
)

然后使用jest.fn()模拟函数的返回值返回并测试错误。

注意:我使用的是 jest 和 mocha,而不是 avoriaz。

【讨论】:

nextTick 甚至没有必要。【参考方案2】:

您需要在测试中使用Vue.nextTickdone

test('should work', (done) => 
  expect(wrapper.data().val).toBe(1);
  wrapper.find('.my-btn')[0].trigger('click');
    Vue.nextTick(() => 
        expect(wrapper.vm.val).toBe(3);
        done()
    )
);

【讨论】:

嗨,艾德!非常感谢您回答我。但我仍然没有运气。我更新了我的问题,看来val 仍然是 1... 我忘记在回调中添加 done,我已经编辑了我的答案。你能再试一次吗? 嗨,Edd,我又试了一次,但仍然出错。请检查我的新更新。 这个错误看起来你没有在下一个滴答回调中调用完成?由于我现在正在休假八,我无法正确查看,但解决方案应该使用 done 和 nextTick

以上是关于使用 jest 和 avoriaz 时如何在 Vuejs 的组件中测试异步方法的主要内容,如果未能解决你的问题,请参考以下文章

使用带有 Avoriaz 的 AVA 在 Vue.js 中测试计算属性

Vue Typescript 组件 Jest 测试不会在 html 中呈现值

Jest-vue 和 Vue-jest 有啥区别?

在 Jest 中测试失败时如何打印请求和响应?

使用 Jest、Graphql 和 Sequelize 测试数据库时如何避免 EADDRINUSE

使用 MSW 和 Jest 时如何在请求之间的测试中清除 RTK 查询缓存?