Unit testing Vue SFC
The what, why, and how
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)
})


- Fast to run
 - Easy to understand
 - Only test a single unit of work
 - Fast to run
 - Easy to understand
 - Only test a single unit of work
 
Unit Tests Should be
Why?
Unit test benefits
- Check components work correctly
 - Provide documentation
 - Easier debugging (TDD)
 - Less bugs
 
How?
<template>
  <div></div>
</template>
<script>
  export default {
    props: ['visible']
  }
</script>SFC
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
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
$ yarn add -D jest vue-jest ts-jest// package.json
{
// ..
  "jest": {
    "transform": {
      "^.+\\.ts$": "ts-jest",
      "^.+\\.vue$": "vue-jest"
    },
  }
// ..
}// package.json
{
// ..
  "scripts": {
    "unit": "jest"
  }
// ..
}$ yarn unitvue-test-utils
$ yarn add -D @vue/test-utilshttps://vue-test-utils.vuejs.org/
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

<cb-modal-component
  :visible="displayModal"
  :onClose="closeModal"
>
  Hello Chargebee
</cb-modal-component>test('does not render when not passed visible prop', () => {
})test('- :visible does not render when not passed visible prop', () => {
  const wrapper = mount(Modal)
})test(':visible - 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(':visible - renders when passed visible prop as true', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
})test(':visible - renders when passed visible prop as true', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
  expect(wrapper.isEmpty()).toBe(false)
})test('close - Emits when when button is clicked', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
  wrapper.find('button').trigger('click')
  expect(wrapper.emitted().close).toHaveBeenCalled()
})test('calls onClose when button is clicked', () => {
  const onClose = jest.fn()
})test('close - Emits when when button is clicked', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
})test('close - Emits when when button is clicked', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
  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
// 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"
    ],
  // ..
  }
  // ..
}$ yarn add -D jest-serializer-vue

$ yarn unit -- -uOverwrite 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()
})
Resources
- vue-test-utils.vuejs.org
 - jestjs.io
 - github.com/kulshekhar/ts-jest
 - github.com/vuejs/vue-jest
 - github.com/eddyerburgh/jest-serializer-vue
 - www.manning.com/books/testing-vue-js-applications
 
Vue Unit Testing
By Bharathvaj Ganesan
Vue Unit Testing
What, why, and how of unit testing Vue components
- 689