This website requires JavaScript.

vue 单文件测试

2017.12.29 12:27字数 5412阅读 1536喜欢 15评论 12

前言

官网虽有测试例子,但涉及较窄,遇到组件中存在异步传参、触发 action、获取 state 等问题时,编写单元测试便不知从哪下手。

这篇文章结合实际项目,旨在解决上述问题,顺便记录写测试文件时遇到的一些问题,希望对各位朋友有所帮助。

当然,最重要的问题是:为什么要写测试?

于我,大概就是:如果写测试不是为了装逼,那将毫无意义 对写的程序更自信吧。

环境

  • vue-cli@2.9.2 配置 Jest 测试;
  • 使用插件 vue-test-utils ,提供丰富的 api ,Vue 团队维护 。

      npm install --save-dev vue-test-utils
    
  • 正常情况下,test 目录如果像下图一样,那么接下来就可以在 spaces 文件夹里编写测试用例了。

简单组件实例

  • template 部分

    <div>
      <el-input 
        v-model="username"></el-input>
    
      <el-input
        v-model="password"
        @keyup.enter.native="submit"></el-input>
    
      <el-button 
        @click.native="submit"
        :disabled="logining">
          {{ logining ? 'login...' : 'Submit' }}
      </el-button>
    </div>
    
  • script 部分

    export default {
      name: 'Login',
    
      data () {
        return {
          username: '',
          password: ''
        }
      },
    
      computed: {
        logining () {
          return this.$store.state.login
        }
      },
    
      methods: {
        async submit () {
          const res = await this.$store.dispatch('login', {
            username: this.username,
            password: this.password
          })
          if (res.code === 1) this.$router.push('/index')
          return res
         }
      }
    }
    

编写测试用例

// login.spec.js
// 使用了 element-ui , 需要引入
import Vue from 'vue'
import ElementUI from 'element-ui'

import Login from '@/components/login'

Vue.use(ElementUI)

mock action and state

在这个组件里,会调用 Vuex action ,以及 state ,为了完成测试,需要给 Vue 传递一个伪造的 Store 。

import Vuex from 'vuex'
import { mount, createLocalVue } from 'vue-test-utils'

// 创建独立作用域 Vue ,避免影响全局
const localVue = createLocalVue()

localVue.use(Vuex)

descript('Login.vue', () => {
  let actions
  let state
  let store

  beforeEach(() => {
    state = {
      login: false
    }
    actions = {
      login: jest.fn()  // mock function
    }
    store = new Vuex.Store({
      state,
      actions
    })
  })
})

getter, mutation 同理。

mock router

当组件中使用 $route 或者 $router 时,并不推荐安装 Vue Router,因为安装之后也只是在 Vue 的原型上添加 $route 和 $router 只读属性,这意味着伪造 $route 或 $router 都会失效。
取而代之,只需 mock $route 和 mock $router。

const $route = {
  path: '/some'
  // ...其他属性
}

const $router = {
  push: jest.fn()
  // ... 其他属性
}

const wrapper = mount(Login, {
  mocks: {
    $route,
    $router
  }
})

测试计算属性 logining

it('The button className should contain "is-disabled" when "loging" is true', () => {
  const wrapper = mount(Login, {
     store,
     localVue,
     mocks: {
       $route,
       $router
     }
  })
  cont btn = wrapper.find('.el-button')

  // btn class 没有 is-disabled
  expect(btn.classes()).not.toContain('is-disabled')

  wrapper.setComputed({
    logining: true
  })

  // 重新渲染
  wrapper.update()
  expect(btn.classes()).toContain('is-disabled')
})

submit 方法测试

在这个简单组件中,需要测试 input 键盘按下,以及 button 点击是否触发 submit 方法。

it('Submit method shoud be called', () => {
  const wrapper = mount(Login, {
     store,
     localVue,
     mocks: {
       $route,
      $router
     }
  })
  const btn = wrapper.find('.el-button')
  const input = wrapper.findAll('.el-input').at(1) // 第二个

  // 伪造一个jest的 mock funciton
  const stub = jest.fn()
  wrapper.setMethods({ submit: stub })

  btn.trigger('click')
  expect(stub).toBeCalled()

  input.trigger('keyup', { which: 13 })
  // input.trigger('keyup.enter')
  expect(stub).toBeCalled()
})

mock funcion

最简单的 mock function 的写法,在上文中已经写出:jest.fn() 。

如果要指定返回内容,可以写成以下方式:

jest.fn(() => 'some value')

在实际应用里,请求结果的不确定性,以致并不能用以上方法来 mock 请求。

查阅相关资料后,发现如下方法,可以满足一个方法,输出不同结果的需求。

const myMockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call')

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn())
// > 'first call', 'second call', 'default', 'default'

用于例子组件中,只需改动测试的 action 即可:

actions = {
  login: jest
        .fn(() => Promise.resolve({
          code: 1,
          message: '登录成功',
          result: ''
         }))
        .mockImplementationOnce(() => Promise.resolve({
          code: 0,
          message: '登录失败',
          result: ''
        }))
}

编写测试:

it ('Mock function', async () => {
  const wrapper = mount(Login, {
     store,
     localVue,
     mocks: {
       $route,
       $router
     }
  })

  // 第一次调用函数时,登录失败
  const res = await wrapper.vm.submit()
  expect(res.code).toBe(0)

  // 第二次调用函数时,登录成功
  const otherRes = await wrapper.vm.submit()
  expect(res.code).toBe(1)
})

测试快照

jest 有一个提供快照的功能,它能够将某个状态下的 html 结构以一个快照文件的形式存储下来,以后每次运行快照测试的时候如果发现跟之前的快照测试的结果不一致,测试就无法通过。

如果页面确定需要改变,只需要运行测试的时候加上 -u 参数,更新快照即可。

it('Has the expected html structure', () => {
  expect(wrapper.element).toMatchSnapshot()
})

第一次运行快照时,会创建一个 __snapshots__ 目录存放快照文件。

其他

诸如 props ,emit 的测试, vue-test-utils 上已经有详细的例子,也就不再重复。

这里有测试的例子: https://github.com/jkchao/vue-admin

参考