Vue.js

The Practical Guide

@N_Tepluhina

 Testing pyramid

@N_Tepluhina

Unit test runners

- Karma with Mocha or Jasmine

- Jest (recommended)

@N_Tepluhina

Installation

# On existing Vue CLI project

vue add unit-jest

# Anywhere

npm install --save-dev @vue/test-utils vue-jest
# OR
yarn add -D @vue/test-utils vue-jest

@N_Tepluhina

Manual installation

npm install --save-dev jest
# OR
yarn add -D jest

@N_Tepluhina

Manual installation

npm install --save-dev babel-jest @babel/core @babel/preset-env
# OR
yarn add -D babel-jest @babel/core @babel/preset-env

@N_Tepluhina

Manual installation

// babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
  ],
};

@N_Tepluhina

Manual installation

npm install --save-dev @vue/test-utils vue-jest
# OR
yarn add -D @vue/test-utils vue-jest

@N_Tepluhina

Manual installation

// package.json

{
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      // tell Jest to handle `*.vue` files
      "vue"
    ],
    "transform": {
      // process `*.vue` files with `vue-jest`
      ".*\\.(vue)$": "vue-jest"
    }
  }
}

@N_Tepluhina

Writing the very first component test

// MyComponent.spec.js

import { shallowMount } from "@vue/test-utils";
import MyComponent from "@/components/MyComponent.vue";

describe("MyComponent", () => {
  let wrapper;

  it("is a Vue component", () => {
    wrapper = shallowMount(MyComponent);

    expect(wrapper.exists()).toBe(true);
  });
});

@N_Tepluhina

Isolating test cases

// MyComponent.spec.js

// component factory
function createComponent() {
  wrapper = shallowMount(MyComponent);
}

// destroying a wrapper
afterEach(() => {
  wrapper.destroy();
  wrapper = null;
});

@N_Tepluhina

Testing conditional rendering

// MyComponent.spec.js

it('does not render title when showTitle prop is false', () => {
  createComponent()

  expect(wrapper.find("[data-testid='title']").exists()).toBe(false)
})

@N_Tepluhina

Passing component props

function createComponent(props = {}) {
  wrapper = shallowMount(MyComponent, {
    propsData: {
      ...props,
    },
  })
}

...

createComponent({ showTitle: true })

@N_Tepluhina

Abstracting finder helpers

let wrapper

const findTitle = () => wrapper.find("[data-testid='title']")

@N_Tepluhina

Triggering native events

const findTitle = () => wrapper.find("[data-testid='title']")
const findIncrementButton = () => wrapper.find('.test-increment-button')

...

it('increments count on increment button click', () => {
  createComponent()

  expect(findCount().text()).toBe('Count: 0')
  findIncrementButton().trigger('click')
})

@N_Tepluhina

Dealing with DOM updates

const findTitle = () => wrapper.find("[data-testid='title']")
const findIncrementButton = () => wrapper.find('.test-increment-button')

...

it('increments count on increment button click', async () => {
  createComponent()

  expect(findCount().text()).toBe('Count: 0')
  findIncrementButton().trigger('click')
  await wrapper.vm.$nextTick
  expect(findCount().text()).toBe('Count: 1')
})

@N_Tepluhina

Testing computed property

Rule of thumb: follow the user!

@N_Tepluhina

Testing computed property

  it('renders a correct doubled value', () => {
    createComponent()

    expect(findDoubleCount().text()).toContain('0')
  })

@N_Tepluhina

Setting component data

function createComponent(props = {}, data = {}) {
  wrapper = shallowMount(MyComponent, {
    propsData: {
      ...props,
    },
    data() {
      return {
        ...data,
      }
    },
  })
}

@N_Tepluhina

Setting component data

it('renders a correct doubled value', () => {
  createComponent({}, { count: 3 })

  expect(findDoubleCount().text()).toContain('6')
})

@N_Tepluhina

Checking component emitted events

it('emits a custom event on emitter button click', () => {
  createComponent()
  wrapper.find('.test-emitter').trigger('click')

  expect(wrapper.emitted('custom-event')).toBeTruthy()
  expect(wrapper.emitted('custom-event')).toEqual([['Hello World']])
})

@N_Tepluhina

Testing a watcher

it('emits an event on showTitle change', async () => {
  createComponent()

  wrapper.setProps({ showTitle: true })
  await wrapper.vm.$nextTick
  expect(wrapper.emitted('watcher-triggered')).toBeTruthy()
})

@N_Tepluhina

Dealing with child components

- shallowMount vs. mount

- deal with component as a black box

- remember to work with component contracts: props and events

@N_Tepluhina

Dealing with child components

it('changes childCounter on MyButton click', async () => {
  createComponent()

  wrapper.findComponent(MyButton).vm.$emit('click')
  await wrapper.vm.$nextTick()
  expect(findChildCounter().text()).toContain('2')
})

@N_Tepluhina

Router

function createComponent(props = {}, data = {}) {
  wrapper = shallowMount(MyComponent, {
    propsData: {
      ...props,
    },
    data() {
      return {
        ...data,
      }
    },
    mocks: {
      $route: {
        path: '/',
      },
    },
    stubs: ['router-link'],
  })
}

@N_Tepluhina

Router

import { shallowMount, mount, createLocalVue } from '@vue/test-utils'

const localVue = createLocalVue()
localVue.use(VueRouter)

const router = new VueRouter({
  routes: [{ path: '/', component: MyComponent }],
})


function createComponent(props = {}, data = {}) {
  wrapper = shallowMount(MyComponent, {
    localVue,
    router,
    ...
  })
}

Q & A

@N_Tepluhina

Vue.js: The Practical Guide

By Natalia Tepluhina

Vue.js: The Practical Guide

Smashing Workshop - Day 5

  • 744