How Vue Unit Testing Works and 4 Critical Best Practices

Oliver Moradov

What is Vue.js Unit Testing?

When building a Vue.js application, unit testing is important to ensure the quality of your code and prevent regressions. Vue unit testing involves breaking your application into functions, modules, classes, and components, each of which can be tested independently. By writing unit tests for your components and running them with each build, you can catch issues early and fix them early in the development lifecycle.

To write a unit test in Vue.js, you create a file with the same name as the code you want to test, adding the extension spec.js. Within the unit test, you do the following:

  • Import a function from the original file.
  • Add a describe block to specify what is the desired functionality.
  • Add one or more test statements, each of which checks if the function returns the correct output given a certain input.

Vue Test Utils is an official helper library provided by Vue that lets you mount and render Vue.js components using the concept of wrappers. 

Related content: Read our guide to unit testing in JavaScript applications (coming soon)

In this article:

How Unit Testing Works in Vue.js

In Vue.js, a unit test usually covers a single function, class, composable, or module. The purpose of a unit test is to check correctness and business logic for a small part of the overall application. It is common to “mock out” other parts of the environment, such as the initial state, network requests, and third-party modules.

Let’s see how to test a simple function in Vue.js. These code examples were shared in the Vue documentation.

The following function increments an integer until a maximum number is reached. Let’s say this function is part of the file example.js.

export function increment (current, max = 10) {
  if (current < max) {
    return current + 1
  }
  return current
}

We’ll create a unit test for this function, which will assert that given different inputs, it returns the correct output. If an assertion fails, it will be clear that the problem is with this increment function.

The unit test will be in the file example.spec.js. It first imports the original function so we can invoke and test it:

import { increment } from './example'

This code block uses the describe function to state what is the desired functionality of the unit under test:

describe('increment', () => {
  test('increments the integer by 1', () => {
    expect(increment(0, 10)).toBe(1)
  })

The following test checks if the function really stops at the maximum number:

  test('does not increment the provided integer over the max', () => {
    expect(increment(10, 10)).toBe(10)
  })

The following test checks if the function has a default maximum number of 10:

  test('default max number is 10', () => {
    expect(increment(10)).toBe(10)
  })
})

Testing Vue-Specific Features: Composables and Components

Testing Vue Composables

A Vue Composable reuses stateful logic using the Vue Composition API. This allows you to manage functionality with constantly changing state, such as touch gestures or database connection states. This is in contrast to libraries that reuse stateless logic, such as the ability to format a date.

Testing Composables works differently depending on their use of a host component instance. Composables depend on a host component instance if they use lifecycle hooks or the provide / inject APIs. 

  • If a Composable depends on a host component – you need to wrap it in a host component in order to test it.
  • If a Composable only uses Reactivity APIs – you can test it directly by invoking it and asserting the state or return values of its methods.

If your Composable is more complex, it might be easier to write tests against its wrapper component, effectively treating the Composable as a component (see below).

Testing Vue Components

Components can be tested in two ways.

  • White-box testing—tests component implementation and dependencies. The goal is to isolate the component under test. These tests typically mock some or all of a component’s sub-components, and set up plugin states and dependencies.
  • Black box testing—tests a component without knowing the implementation details. These tests mock out dependencies as little as possible, to test the integration of the component with the entire system. Because these tests render all sub-components, they are not really unit tests but more like integration tests.

What is Vue Test Utils?

Vue Test Utils is an official helper library that enables testing of Vue.js components. It provides several ways to mount and manipulate Vue.js components in an isolated way using the concept of wrappers.

A wrapper is an abstraction of a mounted Vue component. It provides capabilities that are useful for testing, for example triggering a click or an event. You can use it to:

  • Simulate input—for example, user actions or store changes
  • Check if output is correct—for example, by rendering components, triggering Vue events, or calling functions.
  • Mock and stub components—components can be rendered with shallowMount or as stubs.

The wrapper abstraction is very powerful because it lets you access the Vue instance via wrapper.vm, meaning you can gain access to any additional Vue functionality you need for your testing. 

4 Best Practices for Vue Unit Testing

Test Component Interfaces, Not Internals

When you are testing UI components, it is not a good idea to achieve full coverage of all code lines in your component. This will lead to an excessive focus on implementation details, leading to brittle tests that are likely to break every time you change your implementation.

A better approach is to write tests that evaluate the public interface of your component, treating the internal elements as a black box. A test should assert that an input, such as a user action or a change of properties, results in the expected outputs, which may be rendered in the browser or emitted as custom events.

This approach can save time and improve the reliability of tests, because as long as the public interface stays the same, tests will pass, even if the internal implementation of the component has changed.

Shallow Mounting

When you mount a component as part of a unit test, it can be slow and inefficient to mount the entire component with all its dependencies. Vue Test Utils provides a useful method called shallowMount, which lets you mount a component while automatically stubbing its child components, and without actually rendering them. 

Like mount, the shallowMount method provides a wrapper that contains the rendered Vue component, but ensures child components are stubbed out. It looks like this:

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

const wrapper = shallowMount(MyComponent)

It is important to realize that shallow mounting means that the component is different from the one that actually runs in production. Therefore, this will only be appropriate for tests that focus on the independent functions of your component, and do not exercise any child components.

Lifecycle Hooks

It is important to realize that components mounted using either mount or shallowMount will not respond to all lifecycle events. Specifically, hooks defined on the beforeDestroy and destroyed events will not be triggered, until you manually destroy the component.

In addition, a component under test is not automatically destroyed at the end of a spec, and you have to manually clean up any tasks that continue to run.

Asserting Emitted Events

By default, a mounted wrapper records all the events emitted by the Vue instance. This provides useful functionality for unit testing. You can:

  • Retrieve the recorded events using the wrapper.emitted() method
  • Make assertions based on any recorded event
  • Get an Array of events in chronological order using the wrapper.emittedByOrder() method

Related content: Read our guide to unit testing best practices

Vue.js Security Unit Testing with Bright

Bright is a developer-first Dynamic Application Security Testing (DAST) scanner, the first of its kind to integrate into unit testing, revolutionizing the ability to shift security testing even further left. You can now start to test every component / function at the speed of unit tests, baking security testing across development and CI/CD pipelines to minimize security and technical debt, by scanning early and often, spearheaded by developers. With NO false positives, start trusting your scanner when testing your applications and APIs (SOAP, REST, GraphQL), built for modern technologies and architectures. Sign up now for a free account and read our docs to learn more.

Secure your app with every build

Sign up for a FREE Bright account.
Related Articles
Categories