如何使用使用 promise 的方法对 vuejs 组件更新进行单元测试

Posted

技术标签:

【中文标题】如何使用使用 promise 的方法对 vuejs 组件更新进行单元测试【英文标题】:how to unittest a vuejs component update with methods using promise 【发布时间】:2017-12-16 16:29:16 【问题描述】:

我正在用 Jest 测试一个 Vue 组件。这是我的工作目录:

这是我的组件Subscription.vue

<template>
  <div id="page">
    <page-title icon="fa-list-alt">
      <translate slot="title">Subscription</translate>
      <translate slot="comment">Consult the detail of your subscription</translate>
    </page-title>

    <panel v-if="error">
      <span slot="title">
        <icon icon="fa-exclamation-triangle"></icon>
        <translate>Error</translate>
      </span>
       error 
    </panel>
    <panel v-else-if="subscription_dict">
      <span slot="title"> _subscription_address </span>
      <div class="row" v-if="subscription_dict.product.hsi">
        <div class="col-xs-12">
          <subscription-hsi-panel
            :hsi="subscription_dict.product.hsi"
            :business="context.business">
          </subscription-hsi-panel>
        </div>
      </div>
      <div class="row" v-if="subscription_dict.product.itv">
        <div class="col-xs-12">
          <subscription-itv-panel
            :itv="subscription_dict.product.itv">
          </subscription-itv-panel>
        </div>
      </div>
      <div class="row" v-if="subscription_dict.product.voip">
        <div class="col-xs-6">
          <panel icon="icon-voip">
            <translate slot="title">Phone</translate>
            telefon products
          </panel>
        </div>
      </div>
    </panel>

    <div v-else class="text-center">
      <i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>
    </div>

    <span v-if="subscription_dict"><b>subscription_dict.product.voip : </b> subscription_dict.product.voip </br></span>
    <span v-if="subscription_dict"><b>subscription_dict.product : </b> subscription_dict.product </br></span>
  </div>
</template>

<script>
  import PageTitle from '../../core/components/PageTitle.vue'
  import SubscriptionHsiPanel from './SubscriptionHsiPanel.vue'
  import SubscriptionItvPanel from './SubscriptionItvPanel.vue'
  import Panel from '../../core/components/Panel.vue'
  import Icon from '../../core/components/Icon.vue'
  import api from '../../core/api.js'

  export default 
    data() 
      return 
        subscription_dict: false,
        error: false
      
    ,
    props: ['requests_url', 'context'],
    computed: 
      _subscription_address() 
        var sub_address = this.subscription_dict.subscription_address
        return sub_address + ' - ' + this.subscription_dict.package.join(' - ')
      
    ,
    created() 
      this.get_subscription()
      this.translate()
    ,
    methods: 
      get_subscription() 
        let self = this
        api.get_subscription(self.requests_url.subscriptions_request)
        .then(function(response) 
          self.subscription_dict = response
        )
        .catch(function(error) 
          if(error) 
            self.error = error
           else 
            self.error = self.$gettext(
              'We were not able to retrieve your subscription information!')
          
        )
      ,
      translate() 
        this.$gettext('Bridge hsi')
        this.$gettext('Bridge voip_biz')
        this.$gettext('Router')
      
    ,
    components: 
      PageTitle,
      Panel,
      Icon,
      SubscriptionHsiPanel,
      SubscriptionItvPanel
    
  
</script>

<style lang="sass">
  @import "../../core/css/tooltip"

  .table
    table-layout: fixed

    > tbody > tr >
      th
        width: 33%
      td
        vertical-align: middle
</style>

这是我的测试subscription.js

import Vue from 'vue'
import translations from 'src/translations.json'
import GetTextPlugin from 'vue-gettext'
import VTooltip from 'v-tooltip'

import Subscription from 'src/subscription/components/Subscription'

jest.mock('../../core/api');

Vue.use(GetTextPlugin, 
  availableLanguages: 
    en: 'English',
    fr: 'Français',
    de: 'Deutsch',
  ,
  defaultLanguage: 'fr',
  translations: translations
)
Vue.use(VTooltip)

it('render when error', (done) => 
  const renderer = require('vue-server-renderer').createRenderer()
  const vm = new Vue(
    el: document.createElement('div'),
    render: h => h(Subscription, 
      props: 
        requests_url: 
          subscriptions_request: 'error_empty_promise'
        
      
    )
  )
  renderer.renderToString(vm, (err, str) => 
    setImmediate(() => 
      expect(str).toMatchSnapshot()
      done()
    )
  )
)

组件的方法get_subscription()使用我的API函数get_subscription

import axios from 'axios'

export default 
  get_subscription(url) 
    return axios.get(url, 
      credentials: 'same-origin'
      )
      .then(function(response)
        if(response.data.error)
          return Promise.reject(response.data.error)
        else
          return Promise.resolve(response.data)
        
      )
      .catch(function(error) 
        return Promise.reject(false)
      )
  

对于我的测试,我已经像这样模拟了这个函数:

const promises_object = 
  error_empty_promise: new Promise((resolve, reject) => 
    process.nextTick(
      () => reject(false)
    )
  )


export default 
  get_subscription(url) 
    return promises_object[url]
  

现在,在测试中,我渲染并与快照进行比较。我的问题是,在进行比较之前,我找不到等待get_subscription 的承诺是reject 的方法。 结果是我的快照反映了 DOM 更新之前组件的状态,这是在 API 的异步调用之后完成的。 有没有办法告诉 jest 等到 Promisereject 后再调用 expect(str).toMatchSnapshot()

【问题讨论】:

facebook.github.io/jest/docs/tutorial-async.html 谢谢,这个例子我知道了。你能告诉我你如何在我的情况下应用它吗?在示例中,他直接在测试中调用异步函数,因此可以解析Promise 对象。这里我不能这样做,因为当组件状态为created时调用该函数。 创建一个新的 Promise 并返回它。将其设置为在另一个 Promise 的 reject 子句中进行测试和解析。 对不起,我不明白。我已经从我的模拟 API 返回了 Promise。我需要在哪里“创建一个新的 Promise 并返回它”。 ?感谢您迄今为止的帮助! 【参考方案1】:

Jest docs说

it 期望返回值是一个 Promise,它将是 解决了。​​

你有一个将被拒绝的 Promise,所以你需要一个在你的 Promise 被拒绝时解决的 Promise。

  isRejected(rejectingPromise, someExpects) 
    return new Promise((resolve) => 
      rejectingPromise.then(
        () => null,  // if it resolves, fail
        (err) =>    // if it rejects, resolve
          // Maybe do someExpects() here
          resolve();
        
    );
  

【讨论】:

我已尝试使用您的解决方案,但在get_subscription Promise 被拒绝之前仍会创建快照。我仍然有使用微调器制作的快照,而不是错误消息。 也许将快照调用放在nextTick 调用中? 那行不通。 nextTick 调用只等到下一次 DOM 更新。在这里,我们必须等到 Promise 被解决/拒绝。这就是我卡住的地方。 如果你把快照测试放在我把“也许在这里做一些期望”的地方,它不应该在拒绝/解决之前发生。 我不明白,你上面粘贴的get_subscription(url)的代码在src/core/__mocks__/api.js中。这是模拟的 API。使用这个模拟 API 的组件测试在 src/__tests__/subscription/subscription.js 中。那么当it() 语句在subscription.js 中时,相关的expect() 语句怎么会在api.js 中呢?我一定错过了您的解决方案中的某些内容。

以上是关于如何使用使用 promise 的方法对 vuejs 组件更新进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

带有 Jquery 的 VueJS

如何使用 VueJS 限制对未登录用户的页面访问?

如何在Node.js中对使用promises和事件发射器的函数进行单元测试?

Vuejs asyncData函数中的嵌套Promise

如何在数组方法链中展开 Promise 数组?

一文了解Promise使用与实现