Unit testing Vue components

The what, why, and how

Edd Yerburgh

@eddyerburgh

 

What

// sum.js
function sum (a, b) {
  return a + b
}

export default sum
// sum.spec.js
import sum from './sum'

test('returns sum of input', () => {
  expect(sum(1,3)).toBe(4)
})

Why?

Unit test benefits

  • Check components work correctly
  • Provide documentation
  • Easier debugging
  • Less bugs 

How?

<template>
  <div></div>
</template>

<script>
  export default {
    props: ['visible']
  }
</script>
module.exports = {
  render: function () {
    var _vm=this;
    var _h=_vm.$createElement;
    var _c=_vm._self._c||_h;
    return _c('div')
  }
  staticRenderFns: [],
  props: ['visible']
}

compiled SFC

SFC

Compilation process

Modal.vue

Modal.spec.js

Compiler

Test runner

github.com/eddyerburgh/vue-unit-test-perf-comparison

Test runner 1000 5000
tape 9.s 38s
jest 22s 91s
mocha-webpack 11s 38s
karma-mocha 33s 119s
ava 625s 7161s

Test runner comparison

Compilers

  • Webpack (vue-loader)
  • Jest (vue-jest)

Jest

 

vue-test-utils.vuejs.org/en/guides/testing-SFCs-with-jest.html

$ npm install --save-dev jest vue-jest babel-jest
// package.json

{
// ..
  "jest": {
    "transform": {
      "^.+\\.js$": "babel-jest",
      "^.+\\.vue$": "vue-jest"
    },
  }
// ..
}
// package.json

{
// ..
  "scripts": {
    "unit": "jest"
  }
// ..
}
$ npm run unit

compiled SFC

module.exports = {
  render: function () {
    var _vm=this;
    var _h=_vm.$createElement;
    var _c=_vm._self._c||_h;
    return _c('div')
  }
  staticRenderFns: [],
  props: ['visible']
}

vue-test-utils

$ npm install --save-dev @vue/test-utils

mount

import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'

import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'

const wrapper = mount(Modal)

wrapper.vm
wrapper.element
import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'

const wrapper = mount(Modal)

??

Component contract

Input

Output

Component

Input

  • User action
  • Props
  • Store

Output

  • Rendered output
  • Vue events
  • Function calls

Modal component

<modal-component
  :visible="displayModal"
  :onClose="closeModal"
>
  Some content
</modal-component>
test('does not render when not passed visible prop', () => {


})
test('does not render when not passed visible prop', () => {
  const wrapper = mount(Modal)

})
test('does not render when not passed visible prop', () => {
  const wrapper = mount(Modal)
  expect(wrapper.isEmpty()).toBe(true)
})
test('renders when passed visible prop as true', () => {






})
test('renders when passed visible prop as true', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })

})
test('renders when passed visible prop as true', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
  expect(wrapper.isEmpty()).toBe(false)
})
test('calls onClose when button is clicked', () => {
  const onClose = jest.fn()
  const wrapper = mount(Modal, {
    propsData: {
      visible: true,
      onClose
    }
  })
  wrapper.find('button').trigger('click')
  expect(onClose).toHaveBeenCalled()
})
test('calls onClose when button is clicked', () => {
  const onClose = jest.fn()








})
test('calls onClose when button is clicked', () => {
  const onClose = jest.fn()
  const wrapper = mount(Modal, {
    propsData: {
      visible: true,
      onClose
    }
  })


})
test('calls onClose when button is clicked', () => {
  const onClose = jest.fn()
  const wrapper = mount(Modal, {
    propsData: {
      visible: true,
      onClose
    }
  })
  wrapper.find('button').trigger('click')

})
test('calls onClose when button is clicked', () => {









})
test('renders slot content')
<template>
  <div v-if="visible">
      <button @click="onClose" />
  </div>
</template>

<script>
export default {
  props: ['visible', 'onClose']
}
</script>
<template>
  <div v-if="visible" class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <div class="box">
        <button
          @click="onClose"
          class="delete"
          aria-label="close"></button>
        <slot />
        </div>
      </div>
    </div>
  </div>
</template>

Snapshot tests

test('renders correctly', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })

})
test('renders correctly', () => {






})
test('renders correctly', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
  expect(wrapper.html()).toMatchSnapshot()
})

Does previous

snapshot exist?

create snapshot

compare to previous

snapshot

test passes

No

Yes

does output

match

snapshshot?

create snapshot file

test fails

test passes

Compare to previous snapshot

Yes

No

First run 

// Modal.spec.js.snap


exports[`renders correctly 1`] = `"<div class=\\"modal is-active\\"><div class=\\"modal-background\\"></div> <div class=\\"modal-content\\"><div class=\\"box\\"><button aria-label=\\"close\\" class=\\"delete\\"></button> </div></div></div>"`;
// package.json
{
// ..
  "jest": {
    // ..
    "snapshotSerializers": [
      "jest-serializer-vue"
    ],
  // ..
  }
  // ..
}
$ npm install --save-dev jest-serializer-vue
$ npm run unit -- -u

Overwrite snapshot

test('renders correctly', () => {









})
test('renders correctly', () => {
  const wrapper = mount(Modal, {
    slots: {
      default: '<p>some content</p>'
    }



  })

})
test('renders correctly', () => {
  const wrapper = mount(Modal, {
    slots: {
      default: '<p>some content</p>'
    },
    propsData: {
      visible: true
    }
  })

})
test('renders correctly', () => {
  const wrapper = mount(Modal, {
    slots: {
      default: '<p>some content</p>'
    },
    propsData: {
      visible: true
    }
  })
  expect(wrapper.html()).toMatchSnapshot()
})

Get started with the docs

vue-test-utils.vuejs.org/

Learn more with my book 

40% off with code:

ctwvuejsamst18

Unit Testing Vue components

By Edd Yerburgh

Unit Testing Vue components

What, why, and how of unit testing Vue components

  • 7,331