SitePoint
  • Premium
  • Library
  • Community
  • Jobs
  • Blog
LoginStart Free Trial
Testing Vue.js Components with Jest
Testing Vue.js components with Jest
Set up a vue-test sample project
Testing a Component
Testing a Component with vue-test-utils
Adding a Message Component
Testing MessageList with Message Component
The Wrapper object
Properties
Custom Events
Wrapping up
Computed Properties
Watchers
Wrapping Up
Mock External Module Dependencies
Keep mocks externalized
Wrapping Up
Make MessageList slot based
Testing Slots
Testing Named Slots
Testing Contextual Slot Specs
Wrapping
Webpack aliases
Multiple aliases
Other solutions
Wrapping Up
Rethinking in Snapshots
When Snapshot Testing doesn’t help
When a test fails
Conclusion

Write the first Vue.js Component Unit Test in Jest

vue-test-utils, the official VueJS testing library and based on avoriaz, is just around the corner. @EddYerburgh is indeed doing a very good job creating it. It provides all necessary tooling for making easy to write unit test in a VueJS application.

Jest, on the other side, is the testing framework developed at Facebook, which makes testing a breeze, with awesome features such as:

  • Almost no config by default
  • Very cool interactive mode
  • Run tests in parallel
  • Spies, stubs and mocks out of the box
  • Built in code coverage
  • Snapshot testing
  • Module mocking utilities

Probably you’ve already written test without this tools, and just by using karma + mocha + chai + sinon + …, but you’ll see how much easier it can be.

Set up a vue-test sample project

Let’s start by creating a new project using vue-cli answering NO to all yes/no questions:

Code snippet

npm install -g vue-clivue init webpack vue-testcd vue-test

Then we’ll need to install some dependencies:

Code snippet

# Install dependenciesnpm i -D jest vue-jest babel-jest

jest-vue-preprocessor is needed for making jest understand .vue files, and babel-jest for the integration with Babel.

As per vue-test-utils,it hasn’t been released yet, but for now you can add it to your package.json from the source:

Update (2017/10/10): it can be installed already from npm, since beta.1 has been published.

Code snippet

npm i -D @vue/test-utils

Let’s add the following Jest configuration in the package.json:

Code snippet

{  "jest": {    "moduleNameMapper": {      "^vue$": "vue/dist/vue.common.js"    },    "moduleFileExtensions": ["js", "vue"],    "transform": {      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",      ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"    }  }}

moduleFileExtensions will tell Jest which extensions to look for, and transform which preprocessor to use for a file extension.

At last, add a test script to the package.json:

Code snippet

{  "scripts": {    "test": "jest"  }}

Testing a Component

I’ll be using Single File Components here, and I haven’t checked if it works by splitting them in their own html, css or js files, so let’s assume you’re doing that as well.

First create a MessageList.vue component under src/components:

Code snippet

<template>  <ul>    <li v-for="message in messages">      {{ message }}    </li>  </ul></template>
<script>  export default {    name: "list",    props: ["messages"]  };</script>

And update App.vue to use it, as follows:

Code snippet

<template>  <div id="app">    <MessageList :messages="messages" />  </div></template>
<script>  import MessageList from "./components/MessageList";
  export default {    name: "app",    data: () => ({ messages: ["Hey John", "Howdy Paco"] }),    components: {      MessageList    }  };</script>

We have already a couple of components that we can test. Let’s create a test folder under the project root, and a App.test.js:

Code snippet

import Vue from "vue";import App from "../src/App";
describe("App.test.js", () => {  let cmp, vm;
  beforeEach(() => {    cmp = Vue.extend(App); // Create a copy of the original component    vm = new cmp({      data: {        // Replace data value with this fake data        messages: ["Cat"]      }    }).$mount(); // Instances and mounts the component  });
  it('equals messages to ["Cat"]', () => {    expect(vm.messages).toEqual(["Cat"]);  });});

Right now, if we run npm test (or npm t as a shorthand version), the test should run and pass. Since we’re modifying the tests, let’s better run it in watch mode:

Code snippet

npm t -- --watch

The problem with nested components

This test is too simple. Let’s check that the output is the expected as well. For that we can use the amazing Snapshots feature of Jest, that will generate a snapshot of the output and check it against in the upcoming runs. Add after the previous it in App.test.js:

Code snippet

it("has the expected html structure", () => {  expect(cmp.element).toMatchSnapshot();});

That will create a test/__snapshots__/App.test.js.snap file. Let’s open it and inspect it:

Code snippet

// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `<div id="app">  <ul>    <li>      Cat    </li>  </ul></div>`;

If you don’t know very much about snapshots, don’t worry, I’ll cover that up in chapter 9.

In case you haven’t noticed, there is a big problem here: the MessageList component has been rendered as well. Unit tests must be tested as an independent unit, meaning that in App.test.js we want to test App component and don’t care at all about anything else.

This can be the reason of several problems. Imagine for example, that the children components (MessageList in this case) perform side effect operations on the created hook, such as calling fetch, a Vuex action or state changes? That’s something we definitely don’t want.

Luckily, Shallow Rendering solves this nicely.

What is Shallow Rendering?

Shallow Rendering is a technique that assures your component is rendering without children. This is useful for:

  • Testing only the component you want to test (that’s what Unit Test stands for)
  • Avoid side effects that children components can have, such as making HTTP calls, calling store actions…

Testing a Component with vue-test-utils

vue-test-utils provide us with Shallow Rendering among other features. We could rewrite the previous test as follows:

Code snippet

import { shallowMount } from "@vue/test-utils";import App from "../src/App";
describe("App.test.js", () => {  let cmp;
  beforeEach(() => {    cmp = shallowMount(App, {      // Create a shallow instance of the component      data: {        messages: ["Cat"]      }    });  });
  it('equals messages to ["Cat"]', () => {    // Within cmp.vm, we can access all Vue instance methods    expect(cmp.vm.messages).toEqual(["Cat"]);  });
  it("has the expected html structure", () => {    expect(cmp.element).toMatchSnapshot();  });});

And now, if you’re still running Jest in watching mode, you’ll see the test still pass, but the Snapshot doesn’t match. Press u to regenerate it. Open and inspect it again:

Code snippet

// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `<div id="app">  <!--  --></div>`;

You see? Now no children have been rendered and we tested the App component fully isolated from the component tree. Also, if you have any created or whatever hooks in the children components, they haven’t been called either 😉.

If you’re curious about how shallow render is implemented, check out the source code and you’ll see that basically is stubbing the components key, the render method and the lifecycle hooks.

In the same vein, you can implement the MessageList.test.js test as follows:

Code snippet

import { mount } from '@vue/test-utils'import MessageList from '../src/components/MessageList'
describe('MessageList.test.js', () => {  let cmp
  beforeEach(() => {    cmp = mount(MessageList, {      // Be aware that props is overridden using `propsData`      propsData: {        messages: ['Cat']      }    })  })
  it('has received ['Cat'] as the message property', () => {    expect(cmp.vm.messages).toEqual(['Cat'])  })
  it('has the expected html structure', () => {    expect(cmp.element).toMatchSnapshot()  })})

Find the full example of this chapter on Github.

End of PreviewSign Up to unlock the rest of this title.

Community Questions