Unit Testing in Vue.js examples

I’ve really enjoyed working with Vue.js lately but have always struggled with unit-testing. I’m starting to find my feet a little (I think) so wanted to share my approach.

Say we had a new story to develop that went something like this:

As a user, I want to be able to toggle commodity from ‘cost’ and ‘energy’, So that I can get a different view of my graphs.

Note: Each of my components has a bit of a different structure than the normal single file components, html and css. I just find it easier to traverse the file system, and I don’t have to go finding my tests in a random test file. Everything I need it in one component. (more global styles are shared throughout the app using Webpack). This is my structure:

components
    test
        CommoditySelector.spec.js
    CommoditySelector.html
    CommoditySelector.scss
    CommoditySelector.vue
// CommoditySelector.spec.js
import Vue from 'vue'
import CommoditySelector from '../CommoditySelector'
import store from '../../../store'
import { hasClass } from '../../../utils/dom'
import * as actions from '../../../src/store/getters'
import * as mutations from '../../../src/store/getters'
import * as getters from '../../../src/store/getters'
import testAction from '../../../../test/unit/test-action'

describe('CommoditySelector', () => {
  // Nice little helper to return our component within a div
  const getComponent = () => {
    let vm = new Vue({
      template: '<div><test ref="component"></test></div>',
      store,
      components: {
        'test': CommoditySelector
      }
    })

    return vm
  }
  describe('Template', () => {
    it('should have the data hook', () => {
      expect(typeof CommoditySelector.data).toBe('function')
    })
    it('should have a default "commodity" set to "cost"', () => {
      const defaultData = CommoditySelector.data()
      expect(defaultData.commodity).toBe('cost')
    })
    it('should set "toggle-btn--active" to "cost" radio input on mount', () => {
      const vm = getComponent().$mount()
      const toggleCostEl = vm.$el.querySelector('.toggle-btn--cost')
      expect(toggleCostEl).not.toBe(null)
      expect(hasClass(toggleCostEl, 'toggle-btn--active')).toBe(true)
    })
    it('should set "toggle-btn--active" to "energy" radio input on mount', () => {
      store.state.setCommodity.commodity = 'energy'
      const vm = getComponent().$mount()
      const component = vm.$refs.component
      expect(component.commodity).toBe('energy')
      const energyToggleButtonEl = vm.$el.querySelector('.toggle-btn--energy')
      expect(hasClass(energyToggleButtonEl, 'toggle-btn--active')).toBe(true)
    })
    it('should set an "toggle-btn--active" class on commodity change', () => {
      store.state.setCommodity.commodity = 'cost'
      const vm = getComponent().$mount()
      const component = vm.$refs.component
      expect(component.commodity).toBe('cost')
      component.commodity = 'energy'
      component.$nextTick(() => {
        const energyToggleButtonEl = vm.$el.querySelector('.toggle-btn--energy')
        expect(hasClass(energyToggleButtonEl, 'toggle-btn--active')).toBe(true)
      })
    })
    it('should dispatch the "setCommodity" action when setCommodity is called', () => {
      spyOn(store, 'dispatch').and.callFake(() => {})
      const vm = getComponent().$mount()
      const component = vm.$refs.component
      component.setCommodity()
      expect(store.dispatch).toHaveBeenCalled()
      expect(store.dispatch.calls.allArgs()).toEqual([['setCommodity', 'cost']])
    })
  })
  // Vuex Actions
  describe('Actions', () => {
    it('should fire "setCommodity" action', (done) => {
      const payloadMock = 'energy'
      testAction(actions.setCommodity, payloadMock, {}, [
        { type: 'SET_COMMODITY', payload: { commodity: 'energy' } }
      ], done)
    })  
  })
  // Vuex mutations
  describe('Mutations', () => {
    let state
    beforeEach(() => {
      state = {
        commodity: 'cost'
      }
    })
    it('should have a default commodity set to "cost"', () => {
      expect(state.commodity).toBe('cost')
    })
    it('SET_COMMODITY', () => {
      mutations.SET_COMMODITY(state, {
        commodity: 'energy'
      })
      expect(state.commodity).toBe('energy')
    })    
  })
  // Vuex Getters
  describe('Getters', () => {
    it('should return "cost" from the store', () => {
      const state = {
        commodity: 'cost'
      }
      const result = getters.getCommodity(state)
      expect(result).toBe('cost')
    })
    it('should return "energy" from the store', () => {
      const state = {
        commodity: 'energy'
      }
      const result = getters.getCommodity(state)
      expect(result).toBe('energy')
    })
  })
})
// CommoditySelector.vue
<template src="./CommoditySelector.html"></template>
<style lang="scss" src="./CommoditySelector.scss"></style>

<script>
export default {
  name: 'commodity-selector',
  data () {
    return {
      commodity: 'cost'
    }
  },
  computed: {
    getCommodity () {
      return this.$store.getters.getCommodity
    },
    isCostCommdity () {
      return this.commodity === 'cost'
    }
  },
  created () {
    this.commodity = this.getCommodity
  },
  methods: {
    setCommodity () {
      this.$store.dispatch('setCommodity', this.commodity)
    }
  }
}
</script>
<!-- CommoditySelector.html -->
<form class="c-commodity-selector">
    <div class="toggle-wrapper">
        <input 
            id="cost"
            type="radio"
            value="cost"
            v-model="costEnergy"
            v-on:change="setCommodity"
            class="toggle-btn toggle-btn--cost"
            v-bind:class="{ 'toggle-btn--active': isCostCommdity }" 
        />
        <label for="cost" title="Cost" class="cost"></label>
        <input
            id="energy"
            type="radio"
            value="energy"
            v-model="costEnergy"
            v-on:change="setCommodity"
            class="toggle-btn toggle-btn--energy"
            v-bind:class="{ 'toggle-btn--active': !isCostCommdity }"
        />
        <label for="energy" title="Energy" class="energy"></label>
        <i class="green-dot" :class="{ 'green-dot--left': isCostCommdity, 'green-dot--right': !isCostCommdity }"></i>
    </div>
</form>

Hopfully this should end up in some nice green ticks!

testing green ticks

My favourite Vue.js unit-testing resources: