Guides

Getting Started

Setup

To get a quick taste of using Vue Test Utils, clone our demo repository with basic setup and install the dependencies:

git clone https://github.com/vuejs/vue-test-utils-getting-started
cd vue-test-utils-getting-started
npm install

You will see that the project includes a simple component, counter.js:

// counter.js

export default {
  template: `
    <div>
      <span class="count">{{ count }}</span>
      <button @click="increment">Increment</button>
    </div>
  `,

  data () {
    return {
      count: 0
    }
  },

  methods: {
    increment () {
      this.count++
    }
  }
}

Mounting Components

Vue Test Utils tests Vue components by mounting them in isolation, mocking the necessary inputs (props, injections and user events) and asserting the outputs (render result, emitted custom events).

Mounted components are returned inside a Wrapper, which exposes many convenience methods for manipulating, traversing and querying the underlying Vue component instance.

You can create wrappers using the mount method. Let's create a file called test.js:

// test.js

// Import the `mount()` method from the test utils
// and the component you want to test
import { mount } from '@vue/test-utils'
import Counter from './counter'

// Now mount the component and you have the wrapper
const wrapper = mount(Counter)

// You can access the actual Vue instance via `wrapper.vm`
const vm = wrapper.vm

// To inspect the wrapper deeper just log it to the console
// and your adventure with the Vue Test Utils begins
console.log(wrapper)

Test rendered HTML output of the component

Now that we have the wrapper, the first thing we can do is to verify that the rendered HTML output of the component matches what is expected.

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

describe('Counter', () => {
  // Now mount the component and you have the wrapper
  const wrapper = mount(Counter)

  it('renders the correct markup', () => {
    expect(wrapper.html()).toContain('<span class="count">0</span>')
  })

  // it's also easy to check for the existence of elements
  it('has a button', () => {
    expect(wrapper.contains('button')).toBe(true)
  })
})

Now run the tests with npm test. You should see the tests passing.

Simulating User Interaction

Our counter should increment the count when the user clicks the button. To simulate the behavior, we need to first locate the button with wrapper.find(), which returns a wrapper for the button element. We can then simulate the click by calling .trigger() on the button wrapper:

it('button click should increment the count', () => {
  expect(wrapper.vm.count).toBe(0)
  const button = wrapper.find('button')
  button.trigger('click')
  expect(wrapper.vm.count).toBe(1)
})

What about nextTick?

Vue batches pending DOM updates and applies them asynchronously to prevent unnecessary re-renders caused by multiple data mutations. This is why in practice we often have to use Vue.nextTick to wait until Vue has performed the actual DOM update after we trigger some state change.

To simplify usage, Vue Test Utils applies all updates synchronously so you don't need to use Vue.nextTick to wait for DOM updates in your tests.

Note: nextTick is still necessary when you need to explictly advance the event loop, for operations such as asynchronous callbacks or promise resolution.

If you do still need to use nextTick in your test files, be aware that any errors thrown inside it may not be caught by your test runner as it uses promises internally. There are two approaches to fixing this: either you can set the done callback as Vue's global error handler at the start of the test, or you can call nextTick without an argument and return it as a promise:

// this will not be caught
it('will time out', (done) => {
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})

// the two following tests will work as expected
it('will catch the error using done', (done) => {
  Vue.config.errorHandler = done
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})

it('will catch the error using a promise', () => {
  return Vue.nextTick()
    .then(function () {
      expect(true).toBe(false)
    })
})

What's Next

Common Tips

Knowing What to Test

For UI components, we don't recommend aiming for complete line-based coverage, because it leads to too much focus on the internal implementation details of the components and could result in brittle tests.

Instead, we recommend writing tests that assert your component's public interface, and treat its internals as a black box. A single test case would assert that some input (user interaction or change of props) provided to the component results in the expected output (render result or emitted custom events).

For example, for the Counter component which increments a display counter by 1 each time a button is clicked, its test case would simulate the click and assert that the rendered output has increased by 1. The test doesn't care about how the Counter increments the value, it only cares about the input and the output.

The benefit of this approach is that as long as your component's public interface remains the same, your tests will pass no matter how the component's internal implementation changes over time.

This topic is discussed with more details in a great presentation by Matt O'Connell.

Shallow Rendering

In unit tests, we typically want to focus on the component being tested as an isolated unit and avoid indirectly asserting the behavior of its child components.

In addition, for components that contain many child components, the entire rendered tree can get really big. Repeatedly rendering all child components could slow down our tests.

Vue Test Utils allows you to mount a component without rendering its child components (by stubbing them) with the shallowMount method:

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

const wrapper = shallowMount(Component)
wrapper.vm // the mounted Vue instance

Asserting Emitted Events

Each mounted wrapper automatically records all events emitted by the underlying Vue instance. You can retrieve the recorded events using the wrapper.emitted() method:

wrapper.vm.$emit('foo')
wrapper.vm.$emit('foo', 123)

/*
`wrapper.emitted()` returns the following object:
{
  foo: [[], [123]]
}
*/

You can then make assertions based on these data:

// assert event has been emitted
expect(wrapper.emitted().foo).toBeTruthy()

// assert event count
expect(wrapper.emitted().foo.length).toBe(2)

// assert event payload
expect(wrapper.emitted().foo[1]).toEqual([123])

You can also get an Array of the events in their emit order by calling wrapper.emittedByOrder().

Manipulating Component State

You can directly manipulate the state of the component using the setData or setProps method on the wrapper:

wrapper.setData({ count: 10 })

wrapper.setProps({ foo: 'bar' })

Mocking Props

You can pass props to the component using Vue's built-in propsData option:

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

mount(Component, {
  propsData: {
    aProp: 'some value'
  }
})

You can also update the props of an already-mounted component with the wrapper.setProps({}) method.

For a full list of options, please see the mount options section of the docs.

Applying Global Plugins and Mixins

Some of the components may rely on features injected by a global plugin or mixin, for example vuex and vue-router.

If you are writing tests for components in a specific app, you can setup the same global plugins and mixins once in the entry of your tests. But in some cases, for example testing a generic component suite that may get shared across different apps, it's better to test your components in a more isolated setup, without polluting the global Vue constructor. We can use the createLocalVue method to achieve that:

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

// create an extended `Vue` constructor
const localVue = createLocalVue()

// install plugins as normal
localVue.use(MyPlugin)

// pass the `localVue` to the mount options
mount(Component, {
  localVue
})

Note some plugins, like Vue Router, add read-only properties to the global Vue constructor. This makes it impossible to reinstall the plugin on a localVue constructor, or add mocks for these read-only properties

Mocking Injections

Another strategy for injected props is simply mocking them. You can do that with the mocks option:

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

const $route = {
  path: '/',
  hash: '',
  params: { id: '123' },
  query: { q: 'hello' }
}

mount(Component, {
  mocks: {
    // adds mocked `$route` object to the Vue instance
    // before mounting component
    $route
  }
})

Stubbing components

You can override components that are registered globally or locally by using the stubs option:

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

mount(Component, {
  // Will resolve globally-registered-component with
  // empty stub
  stubs: ['globally-registered-component']
})

Dealing with Routing

Since routing by definition has to do with the overall structure of the application and involves multiple components, it is best tested via integration or end-to-end tests. For individual components that rely on vue-router features, you can mock them using the techniques mentioned above.

Detecting styles

Your test can only detect inline styles when running in jsdom.

Testing Key, Mouse and other DOM events

Trigger events

The Wrapper expose a trigger method. It can be used to trigger DOM events.

const wrapper = mount(MyButton)

wrapper.trigger('click')

You should be aware that the find method returns a Wrapper as well. Assuming MyComponent contains a button, the following code clicks the button.

const wrapper = mount(MyComponent)

wrapper.find('button').trigger('click')

Options

The trigger method takes an optional options object. The properties in the options object are added to the Event.

Note that target cannot be added in the options object.

const wrapper = mount(MyButton)

wrapper.trigger('click', { button: 0 })

Mouse Click Example

Component under test

<template>
  <div>
    <button class="yes" @click="callYes">Yes</button>
    <button class="no" @click="callNo">No</button>
  </div>
</template>

<script>
export default {
  name: 'YesNoComponent',

  props: {
    callMe: {
      type: Function
    }
  },

  methods: {
    callYes() {
      this.callMe('yes')
    },
    callNo() {
      this.callMe('no')
    }
  }
}
</script>

Test

import YesNoComponent from '@/components/YesNoComponent'
import { mount } from '@vue/test-utils'
import sinon from 'sinon'

describe('Click event', () => {
  it('Click on yes button calls our method with argument "yes"', () => {
    const spy = sinon.spy()
    const wrapper = mount(YesNoComponent, {
      propsData: {
        callMe: spy
      }
    })
    wrapper.find('button.yes').trigger('click')

    spy.should.have.been.calledWith('yes')
  })
})

Keyboard Example

Component under test

This component allows to increment/decrement the quantity using various keys.

<template>
  <input type="text" @keydown.prevent="onKeydown" v-model="quantity" />
</template>

<script>
const KEY_DOWN = 40
const KEY_UP = 38
const ESCAPE = 27
const CHAR_A = 65

export default {
  data() {
    return {
      quantity: 0
    }
  },

  methods: {
    increment() {
      this.quantity += 1
    },
    decrement() {
      this.quantity -= 1
    },
    clear() {
      this.quantity = 0
    },
    onKeydown(e) {
      if (e.keyCode === ESCAPE) {
        this.clear()
      }
      if (e.keyCode === KEY_DOWN) {
        this.decrement()
      }
      if (e.keyCode === KEY_UP) {
        this.increment()
      }
      if (e.which === CHAR_A) {
        this.quantity = 13
      }
    }
  },

  watch: {
    quantity: function (newValue) {
      this.$emit('input', newValue)
    }
  }
}
</script>

Test

import QuantityComponent from '@/components/QuantityComponent'
import { mount } from '@vue/test-utils'

describe('Key event tests', () => {
  it('Quantity is zero by default', () => {
    const wrapper = mount(QuantityComponent)
    expect(wrapper.vm.quantity).toBe(0)
  })

  it('Cursor up sets quantity to 1', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.trigger('keydown.up')
    expect(wrapper.vm.quantity).toBe(1)
  })

  it('Cursor down reduce quantity by 1', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.vm.quantity = 5
    wrapper.trigger('keydown.down')
    expect(wrapper.vm.quantity).toBe(4)
  })

  it('Escape sets quantity to 0', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.vm.quantity = 5
    wrapper.trigger('keydown.esc')
    expect(wrapper.vm.quantity).toBe(0)
  })

  it('Magic character "a" sets quantity to 13', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.trigger('keydown', {
      which: 65
    })
    expect(wrapper.vm.quantity).toBe(13)
  })
})

Limitations

A key name after the dot keydown.up is translated to a keyCode. This is supported for the following names:

key name key code
enter 13
esc 27
tab 9
space 32
delete 46
backspace 8
insert 45
up 38
down 40
left 37
right 39
end 35
home 36
pageup 33
pagedown 34

Important

Vue Test Utils triggers event synchronously. Consequently, Vue.nextTick is not required.

Choosing a test runner

A test runner is a program that runs tests.

There are many popular JavaScript test runners, and Vue Test Utils works with all of them. It's test runner agnostic.

There are a few things to consider when choosing a test runner though: feature set, performance, and support for single-file component (SFC) pre-compilation. After carefully comparing existing libraries, here are two test runners that we recommend:

  • Jest is the most fully featured test runner. It requires the least configuration, sets up JSDOM by default, provides built-in assertions, and has a great command line user experience. However, you will need a preprocessor to be able to import SFC components in your tests. We have created the vue-jest preprocessor which can handle most common SFC features, but it currently does not have 100% feature parity with vue-loader.

  • mocha-webpack is a wrapper around webpack + Mocha, but with a more streamlined interface and watch mode. The benefits of this setup is that we can get complete SFC support via webpack + vue-loader, but it requires more configuration upfront.

Browser Environment

Vue Test Utils relies on a browser environment. Technically you can run it in a real browser, but it's not recommended due to the complexity of launching real browsers on different platforms. Instead, we recommend running the tests in Node with a virtual browser environment using JSDOM.

The Jest test runner sets up JSDOM automatically. For other test runners, you can manually set up JSDOM for the tests using jsdom-global in the entry for your tests:

npm install --save-dev jsdom jsdom-global

// in test setup / entry
require('jsdom-global')()

Testing Single-File Components

Single-file Vue components (SFCs) require pre-compilation before they can be run in Node or in the browser. There are two recommended ways to perform the compilation: with a Jest preprocessor, or directly use webpack.

The vue-jest preprocessor supports basic SFC functionalities, but currently does not handle style blocks or custom blocks, which are only supported in vue-loader. If you rely on these features or other webpack-specific configurations, you will need to use a webpack + vue-loader based setup.

Read the following guides for different setups:

Resources

Testing Single-File Components with Jest

An example project for this setup is available on GitHub.

Jest is a test runner developed by Facebook, aiming to deliver a battery-included unit testing solution. You can learn more about Jest on its official documentation.

Setting up Jest

We will assume you are starting with a setup that already has webpack, vue-loader and Babel properly configured - e.g. the webpack-simple template scaffolded by vue-cli.

The first thing to do is install Jest and Vue Test Utils:

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

Next we need to define a test script in our package.json.

// package.json
{
  "scripts": {
    "test": "jest"
  }
}

Processing Single-File Components in Jest

To teach Jest how to process *.vue files, we will need to install and configure the vue-jest preprocessor:

npm install --save-dev vue-jest

Next, create a jest block in package.json:

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

Note: vue-jest currently does not support all the features of vue-loader, for example custom block support and style loading. In addition, some webpack-specific features such as code-splitting are not supported either. To use these unsupported features, you need to use Mocha instead of Jest to run your tests, and webpack to compile your components. To get started, read the guide on testing SFCs with Mocha + webpack.

Handling webpack Aliases

If you use a resolve alias in the webpack config, e.g. aliasing @ to /src, you need to add a matching config for Jest as well, using the moduleNameMapper option:

{
  // ...
  "jest": {
    // ...
    // support the same @ -> src alias mapping in source code
    "moduleNameMapper": {
      "^@/(.*)$": "<rootDir>/src/$1"
    }
  }
}

Configuring Babel for Jest

Although latest versions of Node already supports most ES2015 features, you may still want to use ES modules syntax and stage-x features in your tests. For that we need to install babel-jest:

npm install --save-dev babel-jest

Next, we need to tell Jest to process JavaScript test files with babel-jest by adding an entry under jest.transform in package.json:

{
  // ...
  "jest": {
    // ...
    "transform": {
      // ...
      // process js with `babel-jest`
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest"
    },
    // ...
  }
}

By default, babel-jest automatically configures itself as long as it's installed. However, because we have explicitly added a transform for *.vue files, we now need to explicitly configure babel-jest as well.

Assuming using babel-preset-env with webpack, the default Babel config disables ES modules transpilation because webpack already knows how to handle ES modules. However, we do need to enable it for our tests because Jest tests run directly in Node.

Also, we can tell babel-preset-env to target the Node version we are using. This skips transpiling unnecessary features and makes our tests boot faster.

To apply these options only for tests, put them in a separate config under env.test (this will be automatically picked up by babel-jest).

Example .babelrc:

{
  "presets": [
    ["env", { "modules": false }]
  ],
  "env": {
    "test": {
      "presets": [
        ["env", { "targets": { "node": "current" }}]
      ]
    }
  }
}

Placing Test Files

By default, Jest will recursively pick up all files that have a .spec.js or .test.js extension in the entire project. If this does not fit your needs, it's possible to change the testRegex in the config section in the package.json file.

Jest recommends creating a __tests__ directory right next to the code being tested, but feel free to structure your tests as you see fit. Just beware that Jest would create a __snapshots__ directory next to test files that performs snapshot testing.

Coverage

Jest can be used to generate coverage reports in multiple formats. The following is a simple example to get started with:

Extend your jest config (usually in package.json or jest.config.js) with the collectCoverage option, and then add the collectCoverageFrom array to define the files for which coverage information should be collected.

{
  "jest": {
    // ...
    "collectCoverage": true,
    "collectCoverageFrom": [
      "**/*.{js,vue}",
      "!**/node_modules/**"
    ]
  }
}

This will enable coverage reports with the default coverage reporters. You can customise these with the coverageReporters option:

{
  "jest": {
    // ...
    "coverageReporters": ["html", "text-summary"]
  }
}

Further documentation can be found in the Jest configuration documentation, where you can find options for coverage thresholds, target output directories, etc.

Example Spec

If you are familiar with Jasmine, you should feel right at home with Jest's assertion API:

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

describe('Component', () => {
  test('is a Vue instance', () => {
    const wrapper = mount(Component)
    expect(wrapper.isVueInstance()).toBeTruthy()
  })
})

Snapshot Testing

When you mount a component with Vue Test Utils, you get access to the root HTML element. This can be saved as a snapshot for Jest snapshot testing:

test('renders correctly', () => {
  const wrapper = mount(Component)
  expect(wrapper.element).toMatchSnapshot()
})

We can improve the saved snapshot with a custom serializer:

npm install --save-dev jest-serializer-vue

Then configure it in package.json:

{
  // ...
  "jest": {
    // ...
    // serializer for snapshots
    "snapshotSerializers": [
      "jest-serializer-vue"
    ]
  }
}

Resources

Testing Single-File Components with Mocha + webpack

An example project for this setup is available on GitHub.

Another strategy for testing SFCs is compiling all our tests via webpack and then run it in a test runner. The advantage of this approach is that it gives us full support for all webpack and vue-loader features, so we don't have to make compromises in our source code.

You can technically use any test runner you like and manually wire things together, but we've found mocha-webpack to provide a very streamlined experience for this particular task.

Setting Up mocha-webpack

We will assume you are starting with a setup that already has webpack, vue-loader and Babel properly configured - e.g. the webpack-simple template scaffolded by vue-cli.

The first thing to do is installing test dependencies:

npm install --save-dev @vue/test-utils mocha mocha-webpack

Next we need to define a test script in our package.json.

// package.json
{
  "scripts": {
    "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js"
  }
}

A few things to note here:

  • The --webpack-config flag specifies the webpack config file to use for the tests. In most cases, this would be identical to the config you use for the actual project with one small tweak. We will talk about this later.

  • The --require flag ensures the file test/setup.js is run before any tests, in which we can setup the global environment for our tests to be run in.

  • The final argument is a glob for the test files to be included in the test bundle.

Extra webpack Configuration

Externalizing NPM Dependencies

In our tests we will likely import a number of NPM dependencies - some of these modules may be written without browser usage in mind and simply cannot be bundled properly by webpack. Another consideration is externalizing dependencies greatly improves test boot up speed. We can externalize all NPM dependencies with webpack-node-externals:

// webpack.config.js
const nodeExternals = require('webpack-node-externals')

module.exports = {
  // ...
  externals: [nodeExternals()]
}

Source Maps

Source maps need to be inlined to be picked up by mocha-webpack. The recommended config is:

module.exports = {
  // ...
  devtool: 'inline-cheap-module-source-map'
}

If debugging via IDE, it's also recommended to add the following:

module.exports = {
  // ...
  output: {
    // ...
    // use absolute paths in sourcemaps (important for debugging via IDE)
    devtoolModuleFilenameTemplate: '[absolute-resource-path]',
    devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
  }
}

Setting Up Browser Environment

Vue Test Utils requires a browser environment to run. We can simulate it in Node using jsdom-global:

npm install --save-dev jsdom jsdom-global

Then in test/setup.js:

require('jsdom-global')()

This adds a browser environment to Node, so that Vue Test Utils can run correctly.

Picking an Assertion Library

Chai is a popular assertion library that is commonly used alongside Mocha. You may also want to check out Sinon for creating spies and stubs.

Alternatively you can use expect which is now part of Jest, and exposes the exact same API in Jest docs.

We will be using expect here and make it globally available so that we don't have to import it in every test:

npm install --save-dev expect

Then in test/setup.js:

require('jsdom-global')()

global.expect = require('expect')

Optimizing Babel for Tests

Notice that we are using babel-loader to handle JavaScript. You should already have Babel configured if you are using it in your app via a .babelrc file. Here babel-loader will automatically use the same config file.

One thing to note is that if you are using Node 6+, which already supports the majority of ES2015 features, you can configure a separate Babel env option that only transpiles features that are not already supported in the Node version you are using (e.g. stage-2 or flow syntax support, etc.).

Adding a test

Create a file in src named Counter.vue:

<template>
  <div>
    {{ count }}
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      count: 0
    }
  },

  methods: {
    increment () {
      this.count++
    }
  }
}
</script>

And create a test file named test/Counter.spec.js with the following code:

import { shallowMount } from '@vue/test-utils'
import Counter from '../src/Counter.vue'

describe('Counter.vue', () => {
  it('increments count when button is clicked', () => {
    const wrapper = shallowMount(Counter)
    wrapper.find('button').trigger('click')
    expect(wrapper.find('div').text()).toMatch('1')
  })
})

And now we can run the test:

npm run test

Woohoo, we got our tests running!

Coverage

To setup code coverage to mocha-webpack, follow the mocha-webpack code coverage guide.

Resources

Testing Single-File Components with Karma

An example project for this setup is available on GitHub.

Karma is a test runner that launches browsers, runs tests, and reports them back to us. We're going to use the Mocha framework to write the tests. We'll use the chai library for test assertions.

Setting up Mocha

We will assume you are starting with a setup that already has webpack, vue-loader and Babel properly configured - e.g. the webpack-simple template scaffolded by vue-cli.

The first thing to do is install the test dependencies:

npm install --save-dev @vue/test-utils karma karma-chrome-launcher karma-mocha karma-sourcemap-loader karma-spec-reporter karma-webpack mocha

Next we need to define a test script in our package.json.

// package.json
{
  "scripts": {
    "test": "karma start --single-run"
  }
}
  • The --single-run flag tells Karma to run the test suite once.

Karma Configuration

Create a karma.conf.js file in the index of the project:

// karma.conf.js

var webpackConfig = require('./webpack.config.js')

module.exports = function (config) {
  config.set({
    frameworks: ['mocha'],

    files: [
      'test/**/*.spec.js'
    ],

    preprocessors: {
      '**/*.spec.js': ['webpack', 'sourcemap']
    },

    webpack: webpackConfig,

    reporters: ['spec'],

    browsers: ['Chrome']
  })
}

This file is used to configure Karma.

We need to preprocess our files with webpack. to do that, we add webpack as a preprocessor, and include our webpack config. We can use the webpack config file in the base of the project without changing anything.

In our configuration, we run the tests in Chrome. To add extra browsers, see the Browsers section in the Karma docs.

Picking an Assertion Library

Chai is a popular assertion library that is commonly used alongside Mocha. You may also want to check out Sinon for creating spies and stubs.

We can install the karma-chai plugin to use chai in our tests.

npm install --save-dev karma-chai

Adding a test

Create a file in src named Counter.vue:

<template>
  <div>
    {{ count }}
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      count: 0
    }
  },

  methods: {
    increment () {
      this.count++
    }
  }
}
</script>

And create a test file named test/Counter.spec.js with the following code:

import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import Counter from '../src/Counter.vue'

describe('Counter.vue', () => {
  it('increments count when button is clicked', () => {
    const wrapper = shallowMount(Counter)
    wrapper.find('button').trigger('click')
    expect(wrapper.find('div').text()).contains('1')
  })
})

And now we can run the tests:

npm run test

Woohoo, we got our tests running!

Coverage

To setup code coverage to Karma, we can use the karma-coverage plugin.

By default, karma-coverage won't use source maps to map the coverage reports. So we need to use babel-plugin-istanbul to make sure the coverage is mapped correctly.

Install karma-coverage, babel-plugin-istanbul, and cross-env:

npm install --save-dev karma-coverage cross-env

We're going to use cross-env to set a BABEL_ENV environment variable. This way we can use babel-plugin-istanbul when we're compiling for our tests—we don't want to include babel-plugin-istanbul when we compile our production code:

npm install --save-dev babel-plugin-istanbul

Update your .babelrc file to use babel-plugin-istanbul when BABEL_ENV is set to test:

{
  "presets": [
    ["env", { "modules": false }],
    "stage-3"
  ],
  "env": {
    "test": {
      "plugins": ["istanbul"]
    }
  }
}

Now update the karma.conf.js file to use coverage. Add coverage to the reporters array, and add a coverageReporter field:

// karma.conf.js

module.exports = function (config) {
  config.set({
  // ...

    reporters: ['spec', 'coverage'],

    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    }
  })
}

And update the test script to set the BABEL_ENV:

// package.json
{
  "scripts": {
    "test": "cross-env BABEL_ENV=test karma start --single-run"
  }
}

Resources

Testing Asynchronous Behavior

To simplify testing, Vue Test Utils applies DOM updates synchronously. However, there are some techniques you need to be aware of when testing a component with asynchronous behavior such as callbacks or promises.

One of the most common asynchronous behaviors is API calls and Vuex actions. The following examples shows how to test a method that makes an API call. This example uses Jest to run the test and to mock the HTTP library axios. More about Jest manual mocks can be found here.

The implementation of the axios mock looks like this:

export default {
  get: () => Promise.resolve({ data: 'value' })
}

The below component makes an API call when a button is clicked, then assigns the response to value.

<template>
  <button @click="fetchResults" />
</template>

<script>
import axios from 'axios'

export default {
  data () {
    return {
      value: null
    }
  },

  methods: {
    async fetchResults () {
      const response = await axios.get('mock/service')
      this.value = response.data
    }
  }
}
</script>

A test can be written like this:

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
jest.mock('axios')

test('Foo', () => {
  it('fetches async when a button is clicked', () => {
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click')
    expect(wrapper.vm.value).toBe('value')
  })
})

This test currently fails because the assertion is called before the promise in fetchResults resolves. Most unit test libraries provide a callback to let the runner know when the test is complete. Jest and Mocha both use done. We can use done in combination with $nextTick or setTimeout to ensure any promises resolve before the assertion is made.

test('Foo', () => {
  it('fetches async when a button is clicked', (done) => {
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click')
    wrapper.vm.$nextTick(() => {
      expect(wrapper.vm.value).toBe('value')
      done()
    })
  })
})

The reason $nextTick or setTimeout allow the test to pass is because the microtask queue where promise callbacks are processed run before the task queue, where $nextTick and setTimeout are processed. This means by the time the $nextTick and setTimeout run, any promise callbacks on the microtask queue will have been executed. See here for a more detailed explanation.

Another solution is to use an async function and the npm package flush-promises. flush-promises flushes all pending resolved promise handlers. You can await the call of flushPromises to flush pending promises and improve the readability of your test.

The updated test looks like this:

import { shallowMount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Foo from './Foo'
jest.mock('axios')

test('Foo', () => {
  it('fetches async when a button is clicked', async () => {
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click')
    await flushPromises()
    expect(wrapper.vm.value).toBe('value')
  })
})

This same technique can be applied to Vuex actions, which return a promise by default.

Using with TypeScript

An example project for this setup is available on GitHub.

TypeScript is a popular superset of JavaScript that adds types and classes on top of regular JS. Vue Test Utils includes types in the distributed package, so it works well with TypeScript.

In this guide, we'll walk through how to setup a testing setup for a TypeScript project using Jest and Vue Test Utils from a basic Vue CLI TypeScript setup.

Adding TypeScript

First you need to create a project. If you don't have Vue CLI installed, install it globally:

$ npm install -g @vue/cli

And create a project by running:

$ vue create hello-world

In the CLI prompt, choose to Manually select features, select TypeScript, and press enter. This will create a project with TypeScript already configured.

NOTE

If you want a more detailed guide on setting up Vue with TypeScript, checkout the TypeScript Vue starter guide.

The next step is to add Jest to the project.

Setting up Jest

Jest is a test runner developed by Facebook, aiming to deliver a battery-included unit testing solution. You can learn more about Jest on its official documentation.

Install Jest and Vue Test Utils:

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

Next define a test:unit script in package.json.

// package.json
{
  // ..
  "scripts": {
    // ..
    "test:unit": "jest"
  }
  // ..
}

Processing Single-File Components in Jest

To teach Jest how to process *.vue files, we need to install and configure the vue-jest preprocessor:

npm install --save-dev vue-jest

Next, create a jest block in package.json:

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

Configuring TypeScript for Jest

In order to use TypeScript files in tests, we need to set up Jest to compile TypeScript. For that we need to install ts-jest:

$ npm install --save-dev ts-jest

Next, we need to tell Jest to process TypeScript test files with ts-jest by adding an entry under jest.transform in package.json:

{
  // ...
  "jest": {
    // ...
    "transform": {
      // ...
      // process `*.ts` files with `ts-jest`
      "^.+\\.tsx?$": "ts-jest"
    },
    // ...
  }
}

Placing Test Files

By default, Jest will recursively pick up all files that have a .spec.js or .test.js extension in the entire project.

To run test files with a .ts extension, we need to change the testRegex in the config section in the package.json file.

Add the following to the jest field in package.json:

{
  // ...
  "jest": {
    // ...
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$"
  }
}

Jest recommends creating a __tests__ directory right next to the code being tested, but feel free to structure your tests as you see fit. Just beware that Jest would create a __snapshots__ directory next to test files that performs snapshot testing.

Writing a unit test

Now we've got the project set up, it's time to write a unit test.

Create a src/components/__tests__/HelloWorld.spec.ts file, and add the following code:

// src/components/__tests__/HelloWorld.spec.ts
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'

describe('HelloWorld.vue', () => {
  test('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(HelloWorld, {
      propsData: { msg }
    })
    expect(wrapper.text()).toMatch(msg)
  })
})

That's all we need to do to get TypeScript and Vue Test Utils working together!

Resources

Using with Vue Router

Installing Vue Router in tests

You should never install Vue Router on the Vue base constructor in tests. Installing Vue Router adds $route and $router as read-only properties on Vue prototype.

To avoid this, we can create a localVue, and install Vue Router on that.

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

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

shallowMount(Component, {
  localVue,
  router
})

Note: Installing Vue Router on a localVue also adds $route and $router as read-only properties to a localVue. This means you can not use the mocks option to overwrite $route and $router when mounting a component using a localVue with Vue Router installed.

When you install Vue Router, the router-link and router-view components are registered. This means we can use them anywhere in our application without needing to import them.

When we run tests, we need to make these Vue Router components available to the component we're mounting. There are two methods to do this.

Using stubs

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

shallowMount(Component, {
  stubs: ['router-link', 'router-view']
})

Installing Vue Router with localVue

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

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

shallowMount(Component, {
  localVue
})

Mocking $route and $router

Sometimes you want to test that a component does something with parameters from the $route and $router objects. To do that, you can pass custom mocks to the Vue instance.

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

const $route = {
  path: '/some/path'
}

const wrapper = shallowMount(Component, {
  mocks: {
    $route
  }
})

wrapper.vm.$route.path // /some/path

Common gotchas

Installing Vue Router adds $route and $router as read-only properties on Vue prototype.

This means any future tests that try to mock $route or $router will fail.

To avoid this, never install Vue Router globally when you're running tests; use a localVue as detailed above.

Using with Vuex

In this guide, we'll see how to test Vuex in components with Vue Test Utils, and how to approach testing a Vuex store.

Testing Vuex in components

Mocking Actions

Let’s look at some code.

This is the component we want to test. It calls Vuex actions.

<template>
  <div class="text-align-center">
    <input type="text" @input="actionInputIfTrue" />
    <button @click="actionClick()">Click</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default{
  methods: {
    ...mapActions([
      'actionClick'
    ]),
    actionInputIfTrue: function actionInputIfTrue (event) {
      const inputValue = event.target.value
      if (inputValue === 'input') {
        this.$store.dispatch('actionInput', { inputValue })
      }
    }
  }
}
</script>

For the purposes of this test, we don’t care what the actions do, or what the store looks like. We just need to know that these actions are being fired when they should, and that they are fired with the expected value.

To test this, we need to pass a mock store to Vue when we shallowMount our component.

Instead of passing the store to the base Vue constructor, we can pass it to a - localVue. A localVue is a scoped Vue constructor that we can make changes to without affecting the global Vue constructor.

Let’s see what this looks like:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Actions from '../../../src/components/Actions'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('Actions.vue', () => {
  let actions
  let store

  beforeEach(() => {
    actions = {
      actionClick: jest.fn(),
      actionInput: jest.fn()
    }
    store = new Vuex.Store({
      state: {},
      actions
    })
  })

  it('dispatches "actionInput" when input event value is "input"', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    const input = wrapper.find('input')
    input.element.value = 'input'
    input.trigger('input')
    expect(actions.actionInput).toHaveBeenCalled()
  })

  it('does not dispatch "actionInput" when event value is not "input"', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    const input = wrapper.find('input')
    input.element.value = 'not input'
    input.trigger('input')
    expect(actions.actionInput).not.toHaveBeenCalled()
  })

  it('calls store action "actionClick" when button is clicked', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    wrapper.find('button').trigger('click')
    expect(actions.actionClick).toHaveBeenCalled()
  })
})

What’s happening here? First we tell Vue to use Vuex with the localVue.use method. This is just a wrapper around Vue.use.

We then make a mock store by calling new Vuex.store with our mock values. We only pass it the actions, since that’s all we care about.

The actions are jest mock functions. These mock functions give us methods to assert whether the actions were called or not.

We can then assert in our tests that the action stub was called when expected.

Now the way we define the store might look a bit foreign to you.

We’re using beforeEach to ensure we have a clean store before each test. beforeEach is a mocha hook that’s called before each test. In our test, we are reassigning the store variables value. If we didn’t do this, the mock functions would need to be automatically reset. It also lets us change the state in our tests, without it affecting later tests.

The most important thing to note in this test is that we create a mock Vuex store and then pass it to Vue Test Utils.

Great, so now we can mock actions, let’s look at mocking getters.

Mocking Getters

<template>
  <div>
    <p v-if="inputValue">{{inputValue}}</p>
    <p v-if="clicks">{{clicks}}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default{
  computed: mapGetters([
    'clicks',
    'inputValue'
  ])
}
</script>

This is a fairly simple component. It renders the result of the getters clicks and inputValue. Again, we don’t really care about what those getters returns – just that the result of them is being rendered correctly.

Let’s see the test:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Getters from '../../../src/components/Getters'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('Getters.vue', () => {
  let getters
  let store

  beforeEach(() => {
    getters = {
      clicks: () => 2,
      inputValue: () => 'input'
    }

    store = new Vuex.Store({
      getters
    })
  })

  it('Renders "state.inputValue" in first p tag', () => {
    const wrapper = shallowMount(Getters, { store, localVue })
    const p = wrapper.find('p')
    expect(p.text()).toBe(getters.inputValue())
  })

  it('Renders "state.clicks" in second p tag', () => {
    const wrapper = shallowMount(Getters, { store, localVue })
    const p = wrapper.findAll('p').at(1)
    expect(p.text()).toBe(getters.clicks().toString())
  })
})

This test is similar to our actions test. We create a mock store before each test, pass it as an option when we call shallowMount, and assert that the value returned by our mock getters is being rendered.

This is great, but what if we want to check our getters are returning the correct part of our state?

Mocking with Modules

Modules are useful for separating out our store into manageable chunks. They also export getters. We can use these in our tests.

Let’s look at our component:

<template>
  <div>
    <button @click="moduleActionClick()">Click</button>
    <p>{{moduleClicks}}</p>
  </div>
</template>

<script>
import { mapActions, mapGetters } from 'vuex'

export default{
  methods: {
    ...mapActions([
      'moduleActionClick'
    ])
  },

  computed: mapGetters([
    'moduleClicks'
  ])
}
</script>

Simple component that includes one action and one getter.

And the test:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import MyComponent from '../../../src/components/MyComponent'
import myModule from '../../../src/store/myModule'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('MyComponent.vue', () => {
  let actions
  let state
  let store

  beforeEach(() => {
    state = {
      clicks: 2
    }

    actions = {
      moduleActionClick: jest.fn()
    }

    store = new Vuex.Store({
      modules: {
        myModule: {
          state,
          actions,
          getters: myModule.getters
        }
      }
    })
  })

  it('calls store action "moduleActionClick" when button is clicked', () => {
    const wrapper = shallowMount(MyComponent, { store, localVue })
    const button = wrapper.find('button')
    button.trigger('click')
    expect(actions.moduleActionClick).toHaveBeenCalled()
  })

  it('renders "state.inputValue" in first p tag', () => {
    const wrapper = shallowMount(MyComponent, { store, localVue })
    const p = wrapper.find('p')
    expect(p.text()).toBe(state.clicks.toString())
  })
})

Testing a Vuex Store

There are two approaches to testing a Vuex store. The first approach is to unit test the getters, mutations, and actions separately. The second approach is to create a store and test against that. We'll look at both approaches.

To see how to test a Vuex store, we're going to create a simple counter store. The store will have an increment mutation and an evenOrOdd getter.

// mutations.js
export default {
  increment (state) {
    state.count++
  }
}
// getters.js
export default {
  evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'
}

Testing getters, mutations, and actions separately

Getters, mutations, and actions are all JavaScript functions, so we can test them without using Vue Test Utils and Vuex.

The benefit to testing getters, mutations, and actions separately is that your unit tests are detailed. When they fail, you know exactly what is wrong with your code. The downside is that you will need to mock Vuex functions, like commit and dispatch. This can lead to a situation where your unit tests pass, but your production code fails because your mocks are incorrect.

We'll create two test files, mutations.spec.js and getters.spec.js:

First, let's test the increment mutations:

// mutations.spec.js

import mutations from './mutations'

test('increment increments state.count by 1', () => {
  const state = {
    count: 0
  }
  mutations.increment(state)
  expect(state.count).toBe(1)
})

Now let's test the evenOrOdd getter. We can test it by creating a mock state, calling the getter with the state and checking it returns the correct value.

// getters.spec.js

import getters from './getters'

test('evenOrOdd returns even if state.count is even', () => {
  const state = {
    count: 2
  }
  expect(getters.evenOrOdd(state)).toBe('even')
})

test('evenOrOdd returns odd if state.count is odd', () => {
  const state = {
    count: 1
  }
  expect(getters.evenOrOdd(state)).toBe('odd')
})

Testing a running store

Another approach to testing a Vuex store is to create a running store using the store config.

The benefit of testing creating a running store instance is we don't have to mock any Vuex functions.

The downside is that when a test breaks, it can be difficult to find where the problem is.

Let's write a test. When we create a store, we'll use localVue to avoid polluting the Vue base constructor. The test creates a store using the store-config.js export:

// store-config.spec.js

import mutations from './mutations'
import getters from './getters'

export default {
  state: {
    count: 0
  },
  mutations,
  getters
}
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import storeConfig from './store-config'
import { cloneDeep } from 'lodash'

test('increments count value when increment is commited', () => {
  const localVue = createLocalVue()
  localVue.use(Vuex)
  const store = new Vuex.Store(cloneDeep(storeConfig))
  expect(store.state.count).toBe(0)
  store.commit('increment')
  expect(store.state.count).toBe(1)
})

test('updates evenOrOdd getter when increment is commited', () => {
  const localVue = createLocalVue()
  localVue.use(Vuex)
  const store = new Vuex.Store(cloneDeep(storeConfig))
  expect(store.getters.evenOrOdd).toBe('even')
  store.commit('increment')
  expect(store.getters.evenOrOdd).toBe('odd')
})

Notice that we use cloneDeep to clone the store config before creating a store with it. This is because Vuex mutates the options object used to create the store. To make sure we have a clean store in each test, we need to clone the storeConfig object.

Resources