How to test Vue.js plugins and extensions

This post shows how to write tests for Vue.js plugins and extensions by creating Vue.js instances, changing state and validating transformation and expected errors, to continuously verify that everything still works after updates, refactorings and merging contributions.

While building python-boilerplate.com, I recently created a Vue.js plugin for syntax highlighting (vue-highlightjs), and wanted to add tests. For this I spent some time researching and testing how to properly test Vue.js plugins and extensions, and I wanted to summarize and share what I’ve learnt.

Some general thoughts to start off with:

  • Start early with testing!
  • Create real Vue.js instances and test with them.
  • You can get a lot of ideas and best practices from the original Vue.js tests.
  • Jest is a really good testing framework (built by Facebook). The examples in this post use Jest, but you can replace this with any other testing framework of your liking (eg. Mocha/Chai, Karma, etc.).
  • Use a linter to automatically detect common code style and syntax problems.
  • Beware that some common JavaScript parsers and utilities such as UglifyJS (used by several Vue.js templates) do not yet support ES6 syntax, and building projects which include modules with ES6 may fail. Therefore it is recommended to distribute plugin with ES5 compatible syntax. You have two options to deal with this:
    • Use ES5 syntax, and automatically check vailidity with an appropriate linter (eg. eslint-config-es5).
    • Use ES6 syntax and transpile it to ES5 in a build step, using a tool like Babel (see also babel-eslint).

Before we start, here is a couple of good tests as reference:


Installing the dependencies

First of all, install Vue.js and Jest as dev-dependencies:

$ npm install --save-dev jest
$ npm install --save-dev vue

Next step is adding a test script to the package.json:

  ...
  "scripts": {
    "test": "jest"
  },
  ...

The command jest automatically discovers and executes all files ending with .test.js. You can now run the tests with npm test.

To setup automatic linting, install and setup eslint or another linter and add a lint script to package.json. See also ecmaVersion in the eslint parser options, this ES5 .eslintrc or babel-eslint for further information.


Executing the tests

You can run the tests with npm test. This is what the output looks like for the tests of vue-highlightjs:

$ npm test

> [email protected] test /Users/chris/Projects/chris/vue-highlightjs
> jest

 PASS  test/index.test.js
  ✓ highlighting pre-defined code (43ms)
  highlighting dynamic code
    ✓ should highlight initial code (5ms)
    ✓ should highlight code when updating variable (7ms)
    ✓ should updated highlighted block when updating code without any content (4ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        0.812s
Ran all test suites.

Writing the tests

Importing Vue.js

Start with importing the compiler-included build (vue/dist/vue instead of only vue):

var Vue = require('vue/dist/vue')

If you use only 'vue' it’s the build without a template compiler, and you may receive warnings and errors such as this:

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

Constructing Vue instances

This is an easy way to create Vue.js instances for tests based on custom templates:

const vm = new Vue({
    template: '<div><span v-show="foo">hello</span></div>',
    data: { foo: true }
}).$mount()

Creating a Vue instance from a custom component is also easy, as per example from the official unit-testing documentation:

import MyComponent from 'path/to/MyComponent.vue'
const vm = new Vue(MyComponent).$mount()

Checking HTML content and transformations

When you want to check for a certain transformation of the content, you can access the current Vue.js instance and it’s DOM with various methods of the Vue.js instance, such as vm.$el or vm.$children:

// vm.$el.firstChild to get the first child
expect(vm.$el.firstChild.style.display).toBe('')

// vm.$el.innerHTML to get the complete HTML content
expect(vm.$el.innerHTML).toEqual(expect.stringContaining('hello'));

See also the Jest expect docs for more information about the expect syntax.

DOM assertions resulting from a Vue.js state change

Vue.js performs DOM updates asynchronously, assertions on DOM updates resulting from state change will have to be made in a Vue.nextTick callback:

const vm = new Vue(MyComponent).$mount()
vm.message = 'foo'

// wait a "tick" after state change before asserting DOM updates
Vue.nextTick(() => {
    expect(vm.$el.textContent).toBe('foo')
})

Full example testing a custom directive

This is a full example of how you might write one test for a custom directive (assuming the module code is in /lib/ and the tests in /test/):

const Vue = require('vue/dist/vue');
const VueCustomDirective = require('../lib');

Vue.use(VueCustomDirective);

test('update content after changing state', () => {
  const template = `
    <div>
      <span v-customdirective="variable"></span>
    </div>`;

  const vm = new Vue({
    template,
    data: { variable: 123 }
  }).$mount();

  // Change state and wait for one tick until checking
  vm.variable = '567';
  Vue.nextTick(function () {
    expect(vm.$el.innerHTML).toEqual(expect.stringContaining('567'));
  });
})

That’s it.

References:


Let me know via @metachris if you have feedback, questions or feel there is anything missing.