Guides

Getting Started

What is Vue Test Utils?

Vue Test Utils (VTU) is a set of utility functions aimed to simplify testing Vue.js components. It provides some methods to mount and interact with Vue components in an isolated manner.

Let's see an example:

// Import the `mount()` method from Vue Test Utils
import { mount } from '@vue/test-utils'

// The component to test
const MessageComponent = {
  template: '<p>{{ msg }}</p>',
  props: ['msg']
}

test('displays message', () => {
  // mount() returns a wrapped Vue component we can interact with
  const wrapper = mount(MessageComponent, {
    propsData: {
      msg: 'Hello world'
    }
  })

  // Assert the rendered text of the component
  expect(wrapper.text()).toContain('Hello world')
})

Mounted components are returned inside a Wrapper, which exposes methods for querying and interacting with the component under testing.

Simulating User Interaction

Let's imagine a counter component that increments when user clicks the button:

const Counter = {
  template: `
    <div>
      <button @click="count++">Add up</button>
      <p>Total clicks: {{ count }}</p>
    </div>
  `,
  data() {
    return { count: 0 }
  }
}

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:

test('increments counter value on click', async () => {
  const wrapper = mount(Counter)
  const button = wrapper.find('button')
  const text = wrapper.find('p')

  expect(text.text()).toContain('Total clicks: 0')

  await button.trigger('click')

  expect(text.text()).toContain('Total clicks: 1')
})

Notice how the test must be async and that trigger needs to be awaited. Check out the Testing Asynchronous Behavior guide to understand why this is needed and other things to consider when testing asynchronous scenarios.

What's Next

Check out our common tips when writing tests.

Alternatively, you can explore the full API.

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, imagine a 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 should not 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 mounting

Sometimes, mounting a whole component with all its all dependencies might become slow or cumbersome. For example, components that contain many child components.

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'
import Component from '../Component.vue'

const wrapper = shallowMount(Component)

Like mount, it creates a Wrapper that contains the mounted and rendered Vue component, but with stubbed child components.

Notice that using shallowMount will make the component under testing different from the component you run in your application – some of its parts won't be rendered! This is why it is not the suggested way of testing components unless you face performance issues or need to simplify test arrangements.

Lifecycle Hooks

When using either the mount or shallowMount methods, you can expect your component to respond to all lifecycle events. However, it is important to note that beforeDestroy and destroyed will not be triggered unless the component is manually destroyed using Wrapper.destroy().

Additionally, the component will not be automatically destroyed at the end of each spec, and it is up to the user to stub or manually clean up tasks that will continue to run (setInterval or setTimeout, for example) before the end of each spec.

Writing asynchronous tests (new)

By default, Vue batches updates to run asynchronously (on the next "tick"). This is to prevent unnecessary DOM re-renders, and watcher computations (see the docs for more details).

This means that you must wait for updates to run after you change a reactive property. You can do that by awaiting mutation methods like trigger:

it('updates text', async () => {
  const wrapper = mount(Component)
  await wrapper.trigger('click')
  expect(wrapper.text()).toContain('updated')
  await wrapper.trigger('click')
  wrapper.text().toContain('some different text')
})

// Or if you're without async/await
it('render text', done => {
  const wrapper = mount(TestComponent)
  wrapper.trigger('click').then(() => {
    wrapper.text().toContain('updated')
    wrapper.trigger('click').then(() => {
      wrapper.text().toContain('some different text')
      done()
    })
  })
})

Learn more in the Testing Asynchronous Behavior

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().

Emitting Event from Child Component

You can emit a custom event from a child component by accessing the instance.

Component under test

<template>
  <div>
    <child-component @custom="onCustom" />
    <p v-if="emitted">Emitted!</p>
  </div>
</template>

<script>
  import ChildComponent from './ChildComponent'

  export default {
    name: 'ParentComponent',
    components: { ChildComponent },
    data() {
      return {
        emitted: false
      }
    },
    methods: {
      onCustom() {
        this.emitted = true
      }
    }
  }
</script>

Test

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

describe('ParentComponent', () => {
  it("displays 'Emitted!' when custom event is emitted", () => {
    const wrapper = mount(ParentComponent)
    wrapper.find(ChildComponent).vm.$emit('custom')
    expect(wrapper.html()).toContain('Emitted!')
  })
})

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.

Mocking Transitions

Although calling await Vue.nextTick() works well for most use cases, there are some situations where additional workarounds are required. These issues will be solved before the vue-test-utils library moves out of beta. One such example is unit testing components with the <transition> wrapper provided by Vue.

<template>
  <div>
    <transition>
      <p v-if="show">Foo</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  }
}
</script>

You might want to write a test that verifies that Foo is shown, then when show is set to false, Foo is no longer rendered. Such a test could be written as follows:

test('should render Foo, then hide it', async () => {
  const wrapper = mount(Foo)
  expect(wrapper.text()).toMatch(/Foo/)

  await wrapper.setData({
    show: false
  })

  expect(wrapper.text()).not.toMatch(/Foo/)
})

In practice, although we are calling and awaiting setData to ensure the DOM is updated, this test fails. This is an ongoing issue related to how Vue implements the <transition> component, that we would like to solve before version 1.0. For now, there are some workarounds:

Using a transitionStub helper

const transitionStub = () => ({
  render: function(h) {
    return this.$options._renderChildren
  }
})

test('should render Foo, then hide it', async () => {
  const wrapper = mount(Foo, {
    stubs: {
      transition: transitionStub()
    }
  })
  expect(wrapper.text()).toMatch(/Foo/)

  await wrapper.setData({
    show: false
  })

  expect(wrapper.text()).not.toMatch(/Foo/)
})

This overrides the default behavior of the <transition> component and renders the children as soon as the relevant boolean condition changes, as opposed to applying CSS classes, which is how Vue's <transition> component works.

Avoid setData

Another alternative is to simply avoid using setData by writing two tests, with the required setup performed using mount or shallowMount options:

test('should render Foo', async () => {
  const wrapper = mount(Foo, {
    data() {
      return {
        show: true
      }
    }
  })

  expect(wrapper.text()).toMatch(/Foo/)
})

test('should not render Foo', async () => {
  const wrapper = mount(Foo, {
    data() {
      return {
        show: false
      }
    }
  })

  expect(wrapper.text()).not.toMatch(/Foo/)
})

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, mount } 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 exposes 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

  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.key === '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('Up arrow key increments quantity by 1', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.trigger('keydown.up')
    expect(wrapper.vm.quantity).toBe(1)
  })

  it('Down arrow key decrements 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', {
      key: 'a'
    })
    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

Testing Asynchronous Behavior

There are two types of asynchronous behavior you will encounter in your tests:

  1. Updates applied by Vue
  2. Asynchronous behavior outside of Vue

Updates applied by Vue

Vue batches pending DOM updates and applies them asynchronously to prevent unnecessary re-renders caused by multiple data mutations.

You can read more about asynchronous updates in the Vue docs

In practice, this means that after mutating a reactive property, to assert that change your test has to wait while Vue is performing updates. One way is to use await Vue.nextTick(), but an easier and cleaner way is to just await the method that you mutated the state with, like trigger.

// inside test-suite, add this test case
it('button click should increment the count text', async () => {
  expect(wrapper.text()).toContain('0')
  const button = wrapper.find('button')
  await button.trigger('click')
  expect(wrapper.text()).toContain('1')
})

Awaiting the trigger above is the same as doing:

it('button click should increment the count text', async () => {
  expect(wrapper.text()).toContain('0')
  const button = wrapper.find('button')
  button.trigger('click')
  await Vue.nextTick()
  expect(wrapper.text()).toContain('1')
})

Methods that can be awaited are:

Asynchronous behavior outside of Vue

One of the most common asynchronous behaviors outside of Vue is API calls in 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">{{ value }}</button>
</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', () => ({
  get: Promise.resolve('value')
}))

it('fetches async when a button is clicked', () => {
  const wrapper = shallowMount(Foo)
  wrapper.find('button').trigger('click')
  expect(wrapper.text()).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 are settled before the assertion is made.

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

The reason setTimeout allows the test to pass is because the microtask queue where promise callbacks are processed runs before the task queue, where setTimeout callbacks are processed. This means by the time the setTimeout callback runs, any promise callbacks on the microtask queue will have been executed. $nextTick on the other hand schedules a microtask, but since the microtask queue is processed first-in-first-out that also guarantees the promise callback has been executed by the time the assertion is made. See here for a more detailed explanation.

Another solution is to use an async function and a package like 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')

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

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

Why not just await button.trigger() ?

As explained above, there is a difference between the time it takes for Vue to update its components, and the time it takes for a Promise, like the one from axios to resolve.

A nice rule to follow is to always await on mutations like trigger or setProps. If your code relies on something async, like calling axios, add an await to the flushPromises call as well.

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({
      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 return – just that their result 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 "store.getters.inputValue" in first p tag', () => {
    const wrapper = shallowMount(Getters, { store, localVue })
    const p = wrapper.find('p')
    expect(p.text()).toBe(getters.inputValue())
  })

  it('Renders "store.getters.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.clicks" 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 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.js

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

export default {
  state: {
    count: 0
  },
  mutations,
  getters
}
// store-config.spec.js

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 committed', () => {
  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 committed', () => {
  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.

However, cloneDeep is not "deep" enough to also clone store modules. If your storeConfig includes modules, you need to pass an object to new Vuex.Store(), like so:

import myModule from './myModule'
// ...
const store = new Vuex.Store({ modules: { myModule: cloneDeep(myModule) } })

Resources