使用 Jest 对 Vuetify 数据表进行单元测试

Posted

技术标签:

【中文标题】使用 Jest 对 Vuetify 数据表进行单元测试【英文标题】:Unit testing a Vuetify Data Table with Jest 【发布时间】:2020-08-12 12:29:28 【问题描述】:

我已经使用 Vuetify 创建了一个 Vue.js 应用程序,现在我正在尝试对包含 Vuetify 数据表的组件进行单元测试。数据表是使用 Axios 从后端 REST API 填充的,这在我运行应用程序时工作正常,但是在我的单元测试中(我用 Jest 模拟 Axios),永远不会填充数据表

这是我的组件的来源

<template>
  <v-container fluid>
    <v-card>
      <v-card-title>
        Results
        <v-spacer></v-spacer>
        <v-text-field
          v-model="search"
          append-icon="mdi-magnify"
          label="Search"
          single-line
          hide-details
        ></v-text-field>
      </v-card-title>
      <v-data-table
        :headers="headers"
        :items="results"
        :search="search"
        :loading="loading"
        loading-text="Loading results..."
        :custom-sort="customSort"
      >
        <template v-slot:item.startTime="item">formatDate(item.startTime)</template>
        <template v-slot:item.endTime="item">formatDate(item.endTime)</template>
      </v-data-table>
    </v-card>
  </v-container>
</template>

<script>
import axios from 'axios';
import moment from 'moment';

function dateArrayToMoment(date) 
  return moment()
    .year(date[0])
    .month(date[1])
    .date(date[2])
    .hour(date[3])
    .minute(date[4]);


export default 
  name: 'ResultsList',
  data() 
    return 
      loading: true,
      search: '',
      headers: [
         text: 'Task', align: 'start', sortable: false, value: 'title' ,
         text: 'State', value: 'state' ,
         text: 'Start Time', value: 'startTime' ,
         text: 'End Time', value: 'endTime' ,
         text: 'Result', value: 'resultMessage' 
      ],
      results: []
    ;
  ,
  mounted() 
    this.loadResults();
  ,
  methods: 
    async loadResults() 
      try 
        let response = await axios.get('BACKEND_SERVER/results', );
        this.results = response.data;
        this.loading = false;
        // eslint-disable-next-line no-debugger
       catch (error) 
        // eslint-disable-next-line no-console
        console.error(error);
        // you can handle different errors differently
        // or just display an error message instead of your table using a <v-if> tag
        // or navigate to an error page
      
    ,
    formatDate(date) 
      return dateArrayToMoment(date).format('YYYY-MM-DD hh:mm');
    ,
    customSort(items, index, isDesc) 
      items.sort((a, b) => 
        if (index[0] == 'startTime' || index[0] == 'endTime') 
          if (!isDesc[0]) 
            return (
              dateArrayToMoment(b[index]).toDate() -
              dateArrayToMoment(a[index]).toDate()
            );
           else 
            return (
              dateArrayToMoment(a[index]).toDate() -
              dateArrayToMoment(b[index]).toDate()
            );
          
        
      );
      return items;
    
  
;
</script>

这是测试组件的测试规范

import Vue from 'vue'
import Vuetify from 'vuetify'
import ResultsList from '@/components/ResultsList'
import  mount, createLocalVue  from '@vue/test-utils'
import axios from 'axios'
import flushPromises from 'flush-promises';

Vue.use(Vuetify)

const localVue = createLocalVue()

var results = [
  
    'id': 1,
    'state': 'COMPLETED',
    'startTime': [2020, 4, 21, 19, 42],
    'endTime': [2020, 4, 21, 19, 42],
    'type': 'Example Scheduled Task',
    'title': 'Example Scheduled Task at 2020-04-21 19:42:00',
    'resultMessage': 'Task finished successfully'
  ,
  
    'id': 2,
    'state': 'COMPLETED',
    'startTime': [2020, 4, 22, 13, 36],
    'endTime': [2020, 4, 22, 13, 36],
    'type': 'Example Scheduled Task',
    'title': 'Example Scheduled Task at 2020-04-22 13:36:00',
    'resultMessage': 'Task finished successfully'
  ,
  
    'id': 3,
    'state': 'COMPLETED',
    'startTime': [2020, 4, 22, 13, 37],
    'endTime': [2020, 4, 22, 13, 37],
    'type': 'Example Scheduled Task',
    'title': 'Example Scheduled Task at 2020-04-22 13:37:00',
    'resultMessage': 'Task finished successfully'
  
];

// Use Jest to mock the Axios
jest.mock('axios');

describe('ResultsList.vue', () => 
  let vuetify

  beforeEach(() => 
    vuetify = new Vuetify()

    axios.get.mockResolvedValue(results);
  )

  it('should have a custom title and match snapshot', async () => 
    const wrapper = mount(ResultsList, 
      localVue,
      vuetify,
      propsData: 
        title: 'Foobar',
      ,
    )

    await flushPromises()

    // With jest we can create snapshot files of the html output
    expect(wrapper.html()).toMatchSnapshot()

  )
)

如您所见,我使用 Jest 模拟 Axios,因此它返回一些测试数据并使用 Jest 验证快照。

问题是快照不包含任何数据(测试或其他),尽管调用了 flushPromises 以确保在拍摄快照之前解决所有承诺。

这是快照。如您所见,数据表、测试或其他方式中没有显示任何数据。

// Jest Snapshot v1

exports[`ResultsList.vue should match snapshot 1`] = `
<div class="container container--fluid" title="Foobar">
  <div class="v-card v-sheet theme--light">
    <div class="v-card__title">
      Results
      <div class="spacer"></div>
      <div class="v-input v-input--hide-details theme--light v-text-field v-text-field--single-line">
        <div class="v-input__control">
          <div class="v-input__slot">
            <div class="v-text-field__slot"><label for="input-4" class="v-label theme--light" style="left: 0px; position: absolute;">Search</label><input id="input-4" type="text"></div>
            <div class="v-input__append-inner">
              <div class="v-input__icon v-input__icon--append"><i aria-hidden="true" class="v-icon notranslate mdi mdi-magnify theme--light"></i></div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="v-data-table theme--light">
      <div class="v-data-table__wrapper">
        <table>
          <colgroup>
            <col class="">
            <col class="">
            <col class="">
            <col class="">
            <col class="">
          </colgroup>
          <thead class="v-data-table-header">
            <tr>
              <th role="columnheader" scope="col" aria-label="Task" class="text-start"><span>Task</span></th>
              <th role="columnheader" scope="col" aria-label="State: Not sorted. Activate to sort ascending." aria-sort="none" class="text-start sortable"><span>State</span><i aria-hidden="true" class="v-icon notranslate v-data-table-header__icon mdi mdi-arrow-up theme--light" style="font-size: 18px;"></i></th>
              <th role="columnheader" scope="col" aria-label="Start Time: Not sorted. Activate to sort ascending." aria-sort="none" class="text-start sortable"><span>Start Time</span><i aria-hidden="true" class="v-icon notranslate v-data-table-header__icon mdi mdi-arrow-up theme--light" style="font-size: 18px;"></i></th>
              <th role="columnheader" scope="col" aria-label="End Time: Not sorted. Activate to sort ascending." aria-sort="none" class="text-start sortable"><span>End Time</span><i aria-hidden="true" class="v-icon notranslate v-data-table-header__icon mdi mdi-arrow-up theme--light" style="font-size: 18px;"></i></th>
              <th role="columnheader" scope="col" aria-label="Result: Not sorted. Activate to sort ascending." aria-sort="none" class="text-start sortable"><span>Result</span><i aria-hidden="true" class="v-icon notranslate v-data-table-header__icon mdi mdi-arrow-up theme--light" style="font-size: 18px;"></i></th>
            </tr>
          </thead>
          <tbody>
            <tr class="v-data-table__empty-wrapper">
              <td colspan="5">No data available</td>
            </tr>
          </tbody>
        </table>
      </div>
      <div class="v-data-footer">
        <div class="v-data-footer__select">Rows per page:<div class="v-input v-input--hide-details v-input--is-label-active v-input--is-dirty theme--light v-text-field v-select">
            <div class="v-input__control">
              <div role="button" aria-haspopup="listbox" aria-expanded="false" aria-owns="list-17" class="v-input__slot">
                <div class="v-select__slot">
                  <div class="v-select__selections">
                    <div class="v-select__selection v-select__selection--comma">10</div><input aria-label="$vuetify.dataTable.itemsPerPageText" id="input-17" readonly="readonly" type="text" aria-readonly="false" autocomplete="off">
                  </div>
                  <div class="v-input__append-inner">
                    <div class="v-input__icon v-input__icon--append"><i aria-hidden="true" class="v-icon notranslate mdi mdi-menu-down theme--light"></i></div>
                  </div><input type="hidden" value="10">
                </div>
                <div class="v-menu">
                  <!---->
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="v-data-footer__pagination">–</div>
        <div class="v-data-footer__icons-before"><button type="button" disabled="disabled" class="v-btn v-btn--disabled v-btn--flat v-btn--icon v-btn--round v-btn--text theme--light v-size--default" aria-label="Previous page"><span class="v-btn__content"><i aria-hidden="true" class="v-icon notranslate mdi mdi-chevron-left theme--light"></i></span></button></div>
        <div class="v-data-footer__icons-after"><button type="button" disabled="disabled" class="v-btn v-btn--disabled v-btn--flat v-btn--icon v-btn--round v-btn--text theme--light v-size--default" aria-label="Next page"><span class="v-btn__content"><i aria-hidden="true" class="v-icon notranslate mdi mdi-chevron-right theme--light"></i></span></button></div>
      </div>
    </div>
  </div>
</div>
`;

【问题讨论】:

我很好奇你在这里想出了什么。我的下意识的回答是我想知道当你模拟 Axios 时它是否只返回模拟并忽略你的数据。 另一个建议是,我会将您的代码分成更有用的模块。取出您的 axios HTTP 调用并将其放入该特定 API 的自己的服务中,并使用此组件仅控制数据表。这样在测试时您可以使用嵌入式数据,而不必依赖 Axios。 Axios 是一个外部引用,是的,如果失败,它会阻止您的数据表被填充,但这是 Axios 的问题,而不是您的数据表。 【参考方案1】:

一些想法: 拍摄快照前请致电wrapper.vm.$nextTick()

不要使用Vue.use(),试试localVue.use(Vuetify)

【讨论】:

已经尝试过了,但恐怕没有什么不同

以上是关于使用 Jest 对 Vuetify 数据表进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

在 Nuxt、Vue、jest 和 Vuetify 中为项目编写单元测试

单元测试包含带有插槽的 Vuetify 数据表的 Vue 组件

Vue/Typescript/Jest - Jest 单元测试语法错误:意外的令牌导入

获取导入错误 vuejs + jest + Vuetify

使用 Jest 进行测试时无法执行单击 Vuetify vSwitch

Vuetify Jest 未知自定义元素 <v-*>