JavaScript

Get Started with vue-test-utils and Jest

In late September, Vue.js released the beta of vue-test-utils. Let’s take this opportunity to dive a little deeper and take a look at the official Vue testing solution named vue-test-utils.

We’ll be using Jest for testing. For us at Codeship, it turned out to be an excellent tool that’s fast and reliable when it comes to continuously running JavaScript unit tests.

Set Up Jest and Run Your First Spec

Why choose Jest in the first place? There are several reasons that I found to be major selling points for this test runner.

It comes with batteries included

You almost need nothing else to get running with your specs. Jest comes with an assertion library, mocking functionality, and code-coverage reporter baked in. Of course, those tools use popular solutions like Jasmin and Istanbul under the hood.

These are baked in nicely, so there is no extra effort required to get the best of those tools. Mocking functionality is also a feature that works well and will be even extended in the near future by the Jest team.

No browser necessary

As Jest runs on NodeJS, there is no real browser required for running your JavaScript specs. For specs that actually wouldn’t require a browser environment at all, Jest allows running those directly on the node CLI for maximum power.

For specs that do need a browser environment, Jest provides jsDOM as a possible environment. The latter is configured as default environment. That’s handy when we want to test Vue components.

Mocking is easy

As mentioned above, Jest comes with mocking functionality in place. You’d mock something using the jest.fn() method.

let mocked = jest.fn()

And the API that the mocked library comes with is quite handy. From mocking return values to checking all params the function may receive, Jest has you covered. Also when it comes to tinkering with timeout functions (setTimeout, setInterval) you’ll have some helpful tools at hand. Some examples follow later in this post.

Snapshot testing

Snapshots are great. They allow for testing strings against each other. That string can be a rendered piece of HTML, a complex JSON object, or whatever you come up with. A snapshot compares the output against a saved and verified version of that output and shows a git-diff-like output if something is wrong. But I guess you are now as excited to get started with some specs as I am.

So let’s get to it. Getting started with Jest is quite straight-forward. The following code examples are assuming yarn is installed, and a package.json is ready for you. (Should you still need a package.json, run yarn init -y to create one quickly.)

$ yarn add jest

This command will install Jest for your project. Although we could use it right away by running the command yarn jest, I prefer to have a task called test for that. So add those lines to your package.json.

// package.json

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

This will allow for calling Jest through the yarn test command.

Before actually writing our specs, the system should be set up to support a modern JavaScript workflow. We’ll do that through babel, so here’s what needs to be done:

$ yarn add babel babel-jest babel-preset-env

If you’ve not heard of babel-preset-env, it basically allows compiling the JavaScript based on the browsers you plan to support. Get more info here: babel-preset-env

Be sure to update your .babelrc file accordingly.

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "safari >= 7"]
      }
    }]
  ]
}

Now you’re all set to write some beautiful specs. So let’s get to it.

You can create your specs anywhere in your project. Jest will find everything that’s called *.spec.js or *.test.js automatically.

// coolifier.js 

export default function (str) {
    return `cool${str.trim()}`
}
// coolifier.spec.js

import coolifier from 'coolifier'

describe('coolifier', () => {
    test('makes my cat cooler', () => {
      expect(coolifier('cat')).toBe('coolcat')
    })
})

If you run Jest now, you should get some nice output telling you the spec passed. Yay. The first spec in Jest done. Now to the really cool stuff.

!Sign up for a free Codeship Account

Testing a Vue Component

At Codeship, we have had Vue in production for quite some time already. Testing was not perfect in the beginning. We verified a lot of functionality through rspecs. Are certain elements in place? Will a click trigger following action? And so on.

Basically, we moved “unit testing” into the context of “acceptance testing” what’s not ideal. But the technical setup we had before wasn’t reliable enough when working with async code, promises, and so on, unless we were throwing multiple libraries into the mix. That made the code ugly, hard to maintain, and still not more trustworthy.

With Jest, that became easier, faster, and more reliable. So why not move some “acceptance testing” into the context of “unit testing” and benefit from the tools we have now?

// Component.js

export default {
  name: 'my-status',

  props: {
    status: {
      type: String,
      default: 'loading'
    }
  },

  template: `
    <div :class="localStatus" @click="notify">
      {{ statusMessage }}
    </div>
  `,

  data() {
    return {
      localStatus: this.status
    }
  },

  methods: {
    setStatus(newStatus) {
      this.localStatus = newStatus
    },

    notify() {
      alert(`Your current status: ${this.localStatus}`)
    }
  },

  computed: {
    statusMessage() {
      if(this.localStatus == 'loading')
        return 'Loading status'
      else
        return `Current status: ${this.localStatus.toUpperCase()}`
    }
  },

  mounted() {
    setTimeout( () => {
      this.setStatus('good')
    }, 1000)
  }
}

Here we have a status component that mainly displays whatever status it receives. It would maybe interact with a server to update the status in real life, but for now, we will just use a setTimeout function we added to the mounted hook. Also, this component has a lot of interesting things we could test. Let’s start easy and look at how to check for the passed-in property.

Using the vue-test-utils

First, we need to make use of the vue-test-utils, so add them to your project:

$ yarn add vue-test-utils@1.0.0-beta

Second, create a spec file:

// component.spec.js

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

test('our component comes with a default value for status', () => {
    let wrapper = mount(Component)
    expect(wrapper.vm.status).toBe('loading')
})

Cool! If that spec now runs, it should pass just fine, and the first test is written. But let’s look a little closer at what actually goes on here.

  1. import { mount } from 'vue-test-utils' — For us, this is the entry point to the vue-test-utils. mount allows us to wrap that component and get access to the Vue instance and several helper methods around it. vue-test-utils comes with more methods, but that will be the only one we need for now.
  2. let wrapper = mount(Component) — That’s where we actually wrap our component. The mount method has more tricks up its sleeve, as you’ll see soon.
  3. expect(wrapper.vm.status).toBe('loading') — The important piece of information here is definitely vm. That key gives you access to the actual mounted Vue instance. By default, Vue binds all props, computed properties, and data values as getters to the root object. So if you want to access any of those, that’s the way to go.

Okay, but now we want to make better use of the mount functionality. Vue provides a propsData key for passing in values for unit testing. Luckily those are supported as well in the vue-test-utils.

test('the default status can be overwritten', () => {
    let wrapper = mount(Component, {
      propsData: {
        status: 'custom status'
      }
    })
    expect(wrapper.vm.status).toBe('custom status')
    expect(wrapper.vm.localStatus).toBe('custom status')
  })

Snapshot testing

The next interesting element could be rendered output of our component. As mentioned earlier, there is a thing in Jest called snapshot testing.

test('rendered output should match snapshot', () => {
    let wrapper = mount(Component)
  expect(wrapper.html()).toMatchSnapshot()
})

Running the spec the first time, Jest will print out to the console:

Snapshot Summary
 › 1 snapshot written in 1 test suite.

Before there was nothing to compare against, but now there is a snapshot. By default, those will be stored inside a __snapshot__ folder. That new snapshot should now be verified once to look as expected:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`rendered output should match snapshot`] = `
"<div class=\\"loading\\">
      Loading status
    </div>"
`;

If all looks fine, that spec is now providing a lot of value for us; we can be sure that the rendered output won’t diverge from what our snapshot looks like. Be sure to check those snapshots into your code repository, so that every time the spec is run, we know it’s running against that snapshot.

Traversing the DOM

With vue-test-utils, it’s also quite easy to traverse the DOM. That will be quite handy for testing events.

test('finding DOM nodes', () => {
  let wrapper = mount(Component)
  expect(wrapper.find('div').exists()).toBeTruthy()
  expect(wrapper.is('div')).toBeTruthy()
    expect(wrapper.contains('div')).toBeFalsy()
  expect(wrapper.findAll('div').length).toBe(1)
})

As you see above, there are multiple ways to get information about your DOM through the wrapper. Know that .find and .findAll include the root node while .contains is not.

With the newly found knowledge, we can try to find the root node and fire a click event on it. Thinking ahead though, we face a problem: We need to verify that the click worked. There is no value on the instance we could check afterward.

Mocking and spying

Also mentioned above, Jest comes with nice mocking functionality in place. Our component fires an alert, so let’s spy on that one.

test('fire a click on a node', () => {
    global.alert = jest.fn()
    let wrapper = mount(Component)

    wrapper.find('div').trigger('click')
    
    expect(alert).toHaveBeenCalled()
  expect(alert).toHaveBeenCalledWith('Your current status: loading')
})

That was a breeze, right? But in some cases, it could be quite hard to spy on whatever comes in that method, and ideally, we want to mock that function in the first place because there is an extra spec that handles only that function. That’s a little trickier, but not much. Here it goes:

test.only('mock a function', () => {
  let mockedNotify = jest.fn()
  Component.methods.notify = mockedNotify
    
  let wrapper = mount(Component)
  wrapper.find('div').trigger('click')
  expect(mockedNotify).toHaveBeenCalled()
})

The tricky part here is that we need to set up our mocks on the raw component object, as we can’t manipulate the Vue instance methods when created. To not lose track of the mocked function, it gets stored in an extra variable upfront. If it’s expected that a function returns a value and it’s more complex to mock everything inside, why not mock a return value? That’s quite easy to do as well with Jest.

mockedNotify.mockImplementation(() => ‚Notified‘)

That would return the String Notified whenever we call that method. Sweet!

Instance updates and timeout functions

There is one last thing we need to tackle before wrapping this up. In our component, a mounted function updates our localStatus asynchronously. Of course, the computed property of statusMessage will reevaluate and eventually the rendered output. So let’s see how we can tackle all those things at once.

Luckily, it’s quite easy to mock timeout functions in Jest; it requires nothing more than adding jest.useFakeTimers() at the top of the spec. Afterward, Jest gives you access to running timers, and they can be finished individually. For the ease of usage, we finish all running timers.

jest.useFakeTimers()

test('status should be updated with good', () => {
  let wrapper = mount(Component)
    jest.runAllTimers()

    expect(wrapper.vm.localStatus).toBe('good')
    wrapper.update()
  expect(wrapper.html()).toMatchSnapshot()
})

Here we’ve seen another important method of the wrapper instance: the update() method. As Vue’s internal HTML updates depend on an internal Tick to minimize serenaders, this event runs asynchronously. The wrapper instance allows for calling an explicit update though. We match it against a new snapshot and enjoy our greatly tested component.

The snapshot should look something like this:

exports[`status should be updated with good 1`] = `
"<div class=\\"good\\">
      Current status: GOOD

Ready for More?

There is a lot more ground to cover with the vue-test-utils, such as shallow rendering or mocking nested components. Additionally, Jest allows for some advanced configurations and setup scripts. If you’re interested in more discussion of Vue and Jest, I had the pleasure of speaking about testing Vue components with Jest at the first official Vue Conference in June 2017. The video of my talk is available here:

We’re incredibly proud to be part of the Vue community by sponsoring the Vue Conference and want to say thank-you to all the other sponsors, like GitLab and NativeScript and especially our friends from Monterail. Without them, the event wouldn’t have happened.

Please share your feedback in the comments below, and let me know what you’d prefer for future posts.

Published on Web Code Geeks with permission by Roman Kuba, partner at our WCG program. See the original article here: Get Started with vue-test-utils and Jest

Opinions expressed by Web Code Geeks contributors are their own.

Roman Kuba

Roman Kuba is the main front-end scientist at Codeship. Besides working with various technologies, he loves to talk about tech while teaching or at meetups. In his free time, Roman loves to spend time with his family (Dad since April 2016) and walk their dog Murphy.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button