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 unit

vue-test-utils

$ yarn add -D @vue/test-utils

https://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 -- -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()
})

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
Made with Slides.com