Vue.js 3 Unit Testing Setup & Basics Tutorial

In this Vue tutorial we learn about Unit testing.

We cover how to set up a testing environment and the basics of writing and running tests with the Jest test runner.

Lesson Project

If you want to follow along with the examples, you will need to create an app generated by the Vue CLI with Unit Testing added.

We want to Manually select features.

Example: manual selection
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
  Default (Vue 3) ([Vue 3] babel, eslint)
> Manually select features

Then add the Unit Testing.

Example: unit testing
? Check the features needed for your project:
 (*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 ( ) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
>(*) Unit Testing
 ( ) E2E Testing

Then select Jest as the testing solution.

Example: jest
? Pick a unit testing solution:
  Mocha + Chai
> Jest

The CLI will install the following tools.

  • Jest is the framework used to perform unit testing.
  • Vue CLI Unit Jest Plugin installs and configures the necessary modules for Jest to work in our project.
  • Vue-Jest is a transformer to compile our code to files that Jest will understand before the tests run.
  • Vue Testing Utilities is a set of utility functions that help us test Vue applications.

Each of them can be installed manually into an existing project. To keep things simple for now, we’ll let the CLI do the setup and configuration for us.

What is Unit Testing?

Unit testing checks if smaller sections of our application, like functions or components, work as expected. Unit tests are faster and more reliable.

As an example, let’s consider a shopping app’s basket component. The component needs to calculate the total of all the products in the basket and display it to the user.

The most likely place for it to fail is in the function that calculates the total, so we can test if the function displays the correct output. Such a Unit test might take the following steps.

  1. Pass two values to the function, for example 1 and 1.
  2. Check that the result is 2 and throw an error if it’s not.
  3. End the test.

Unit testing is fast because we can immediately test a small unit of code after making changes. It also helps developers working in teams to quickly understand code they didn’t write.

That said, unit testing does have its own disadvantages.

  • Unit tests make it unappealing to refactor your code. Even smaller changes like splitting a function will require you to rewrite your tests.
  • We can only test if individual sections of code work, we can’t test if multiple sections work together.

File structure and naming

The CLI will create a few new files and folders for us, let’s quickly explore and explain them.

Test file structure

Although we can store test files anywhere in the project, the convention in Vue is to store them in /tests/unit/ in the project’s main directory.

Example: file structure
project-name/
├── public/
├── src/
|
├── tests/
|   ├── unit/
|   |   └── example.spec.js
|
└── jest.config.js

The CLI has created an example test for us in /tests/unit/example.spec.js that should look similar to the following.

Example: tests/unit/example.spec.js
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(HelloWorld, {
      props: { msg }
    })
    expect(wrapper.text()).toMatch(msg)
  })
})

We’re going to write our own test from scratch in a little bit so it’s safe to delete the code in this file.

The jest.config.js file tells Vue to use the Vue-Jest tool as a transformer for our .vue files.

Example: jest.config.js
module.exports = {
  preset: '@vue/cli-plugin-unit-jest',
  transform: {
    '^.+\\.vue$': 'vue-jest'
  }
}

We don’t need to change anything in this config at the moment.

Test file naming

How we name our test files is also very important. Jest (and other frameworks) use file names to find the tests.

A file can be named whatever we want, as long as it includes either .spec , or .test before the file extension.

Example: test file names
name.spec.js // Javascript
name.spec.ts // TypeScript

// or
name.test.js
name.test.ts

The convention in Vue is to use .spec and give the file the same name as the component it’s testing.

For example, if we had a component called HelloWorld , we would name its test file as one of the following.

Example: Vue convention
HelloWorld.spec.js
HelloWorld.spec.ts

How to write a test with the test method

Jest provides us with the test method to write our unit tests.

The test method takes two arguments.

  • A string outline or description of the test being done.
  • A callback that contains the test assertion(s).
Syntax: test method
test('Description', () => {

  // assertion (what we want
  // the test to check for)
})

Let’s see an example. Create a file called example.spec.js in your project’s /tests/unit/ folder. If one already exists from the project generation, delete the contents.

We’ll start with the outline and describe what we want the test to accomplish.

In our case, we want to do something simple, like check if 1 + 1 = 2, so we can write that.

Example: tests/unit/example.spec.js
test('Expect 1 + 1 = 2', () => {

})

The next step is to create an assertion with Jest’s expect API. The expect API uses matchers to compare values and objects.

Syntax: expect
expect(something).to[matcher](value)

For example, if we want to test for exact equality, we would use the toBe matcher.

Syntax: expect.toBe
expect(something).toBe(value)

note The Vue Test Utilities doesn’t provide any matchers. The ones Jest makes available is more than enough to test anything we need.

We’ll use the toBe matcher in our assertion because we know what the exact value of the result should be.

Example: tests/unit/example.spec.js
test('Expect 1 + 1 = 2', () => {

  // expect the result
  // of 1+1 to be 2
  expect(1+1).toBe(2)
})

How to run a test

When the project generated, it created a new testing script in the package.json file.

Example: package.json
"test:unit": "vue-cli-service test:unit",

Executing the command in the terminal will tell Jest to look for any files in our project with .spec or .test , and run them all.

So to test the file we wrote above, all we need to do is run the following command.

Command: test:unit
npm run test:unit

Jest will run the tests and output a message that shows if the they have passed or failed.

Output:
PASS  tests/unit/example.spec.js
 √ Expect 1 + 1 = 2 (3ms)

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

The first line shows if the test failed or passed, and the test file’s location in our project. The second line shows our assertion description and how long it took to run. Everything below that is the test metadata.

How to write a test with the it method

Because Jest is built on top of Jasmine, it has access to Jasmine’s describe it syntax. That means we can use the it method instead of test to write our tests.

They work the same, test is just Jest’s implementation of it. So we don’t need to do anything special here, just replace test with it and everything will still work as expected.

Example: it
it('Expect 1 + 1 = 2', () => {

  // expect the result
  // of 1+1 to be 2
  expect(1+1).toBe(2)
})

It doesn’t matter which method you use, as long as you stay consistent.

How to group tests into a test suite with the describe method

The describe it syntax allows us to group our tests by wrapping them with the describe method.

Syntax: describe it
describe('The car', () => {

  it('is blue', () => {
    expect(color).toBe(blue)
  }),
  it('is fast', () => {
    expect(topSpeed).toBe(300)
  }),
  it('is safe', () => {
    expect(ncap).toBe(5)
  })
})

Because all the tests are about the car, it makes sense to group them. Similarly, we can group a component’s tests.

Because we’re working with Jest, we can use the test method instead of it .

Example: describe test
describe('The car', () => {

  test('is blue', () => {
    expect(color).toBe(blue)
  }),
  test('is fast', () => {
    expect(topSpeed).toBe(300)
  }),
  test('is safe', () => {
    expect(ncap).toBe(5)
  })
})

Again, it doesn’t matter which you use, just stay consistent.

Let’s see it in action by modifying our earlier test to be grouped with describe .

We’ll also add a second test, separating the two with a comma. The second test chains a not matcher to the expectation that works just like a regular conditional != operator.

Example: tests/unit/example.spec.js
describe('Example', () => {

  it('1 + 1 = 2', () => {
    expect(1+1).toBe(2)
  }),
  it('1 + 1 != 3', () => {
    expect(1+1).not.toBe(3)
  })
})

If we run the test, we should see the following output.

Output:
PASS  tests/unit/example.spec.js
 Example
   √ 1 + 1 = 2 (2ms)
   √ 1 + 1 != 3 (1ms)

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

This time we can see the meta data shows 2 tests but still 1 test suite. When we group our tests with describe, they count as a single test suite.

In Vue, the convention is to describe our test suite with the component name. For example, if we had a component called HelloWorld.vue , we would describe its test suite as follows.

Example: test suite description
describe('HelloWorld.vue', () => {

  it('Individual test description', () => {
    // assertion
  })
})

How to skip a test with the xit method

We can skip a test by prefixing the it method with an x.

Syntax: skip test
xit('Description', () => {
  // test will be skipped
})

Technically it’s a different method, but makes it quick and convenient to skip a test.

As an example, let’s skip the second test in our test suite.

Example: tests/unit/example.spec.js
describe('Example', () => {

  it('1 + 1 = 2', () => {
    expect(1+1).toBe(2)
  }),
  xit('1 + 1 != 3', () => {
    expect(1+1).not.toBe(3)
  })
})

Running the test will produce the following output.

Output:
PASS  tests/unit/example.spec.js
 Example
   √ 1 + 1 = 2 (5ms)
   ○ skipped 1 + 1 != 3

Test Suites: 1 passed, 1 total
Tests:       1 skipped, 1 passed, 2 total
Snapshots:   0 total
Time:        4.196s, estimated 14s
Ran all test suites.

This time we can see the second test was skipped from the assertion and the metadata.

Test failures

In Test Driven Development (TDD), we write a failing test before we try to write a passing one. We aren’t going to discuss TDD here. All we want from this approach right now, is knowing that the test works, which a failing test shows us.

When a test fails, Jest will do its best to tell us what went wrong and where.

As an example, let’s change our expectation to be the wrong value.

Example: failing test
test('Expect failure', () => {

  // will fail
  expect(1+1).toBe(3)
})

Of course 1 + 1 doesn’t equal 3 so the test will fail. Let’s see what that looks like.

Output:
FAIL  tests/unit/example.spec.js
 × Expect failure (6ms)

 ● Expect failure

   expect(received).toBe(expected) // Object.is equality

   Expected: 3
   Received: 2

     2 |
     3 |   // will fail
   > 4 |   expect(1+1).toBe(3)
       |               ^
     5 | })
     6 |

     at Object.<anonymous> (tests/unit/example.spec.js:4:15)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        2.253s
Ran all test suites.

The output shows all the same things we get with a passing test, but it also includes the assertion error, the expected and received values, and on which line and column the failure is likely to come from.

So from the output, we know the following.

  • The file that failed.
  • The matcher used.
  • The expected and received values.
  • The line number and column the failure is likely to come from.

All we have to do to fix it, is go to the line and ensure the received value (or behavior) is the same as the expected value (or behavior).

In our case that’s as simple as changing the 3 back into a 2, and the test will pass again.

Sanity test

In some cases a test can fail because the tools we’re using isn’t working correctly. A sanity test is a simple test that should always pass to check that the tools work correctly. If the test doesn’t pass, we’ll know that the problem isn’t with our codebase.

tip It’s recommended to always write a sanity test as the first one in our suite.

We already know our tools work because we’ve been testing throughout the lesson. But to demonstrate, let’s change our example test to something that should always pass.

Example: tests/unit/example.spec.js
test('Sanity test', () => {
  expect(true).toBe(true)
})

We’ll keep this test in our suite throughout the testing process. If it fails at any time, we’ll know that something went wrong with the tools and it’s not our codebase that’s the problem.

Further Reading

For more information on the topics covered in this lesson, please see the relevant sections below.