Testing Vue components: a cheat sheet
When I was asked to implement unit-testing for our (at the time) brand new front-end, I had no idea about testing Vue apps, so I’d like to share what I’ve learned, and hopefully, you’ll pick some stuff up as well. For the code from all of the examples, explore the repo on GitHub.
This article deals with only one thing: unit tests for Vue components — I’ll try to sum up what you need to know to implement unit testing in a medium to large Vue app. This article is not about testing philosophies, the merits or downsides of TDD/BDD, or the software testing life cycle. However, if you have specific functionality that you want to test in your Vue components, read on.
At 3YOURMIND, we use two main open-source tools which are fundamental for writing our unit tests: vue-test-utils and Jest. Jest is a test-running and assertion library released by Facebook, and vue-test-utils is a utility library which is now an official offering from the Vue team and the open-source community.
I’ll assume that you are building your Vue app with webpack (or similar) if you’re reading this. I started off with the simplest Vue scaffold I can think of, the
webpack-simple template from vue-cli (v2). From here, you need to add vue-test-utils and jest. In your terminal, run:
yarn add --dev @vue/test-utils jest
Now we can add a test script to package.json:
For now, it’s enough to just create a folder called ‘test’ and put everything in there. Later on, I’ll outline a sensible folder structure for more ambitious apps. We’ve now got everything ready to write our first unit test, so let’s do that!
Our first test
To keep everything consistent, we name our unit tests identically to the components they test. By default, Jest looks for files which end in either
spec.js. Our only component right now is
App.vue, so let’s create a file in our test folder:
App.test.js, and perform a quick sanity check:
If you run
yarn test now, you should see some lovely green console messages, informing us that 1 equals 1. Here’s a quick breakdown of what’s included in this file:
The describe function is really there to notify us of what is being tested. There should only be one of these in each test file, as we want to maintain a structure where each component has one test suite (basically a group of tests for one component, utility, or whatever). It gets passed the callback function
test, which groups together assertions (the actual tests) inside its own callback function.
As an example, I use the
test function to group together assertions which test how a particular element behaves when given various data to render. Each test file needs at least one
test block, or it will not run. The
test function also has an alias,
it, which you will also commonly see.
expect function asserts that a particular condition is true. Jest provides various matchers to test for things like strict equality (
toBe), object equality (
toEqual), and so on. There are so many that it is worth checking out the Jest docs here to find out more, which explain them better than I can.
So that was a very quick overview of Jest, which tests things for us. We haven’t talked about vue-test-utils at all, which allows us to test Vue components with Jest. Let’s do that now.
Actually testing a Vue component
I’ve created a truly awesome card component:
There’s only one thing to be interested in here: the
data-test attribute on the div. We’ll use this to access the div in our tests, instead of classes or ids, as it won’t change if we refactor our CSS later. That’s not necessary here, as this component is already 100% perfect.
There’s one more piece of setup to do before we can test Vue components. First, run
yarn add--dev babel-jest vue-jest and add the following to your package.json:
You should have a
.babelrc file hanging around somewhere too. Add this to it:
This allows jest to do two things: firstly, to parse .vue files, and parse
import statements correctly. The rest of our codebase will probably use ES Modules, but jest runs in Node, so it requires CommonJS modules. Now, we can write our tests using the same syntax as our components. More information on this can be found in the jest documentation.
That was a lot of setup work, but now we’re good to go. We can test for the presence of certain content in our test like this:
yarn test again and you should find that it passes. This is obviously a really simple case (and one that could be covered by snapshot testing, which I’ll talk about later). More usefully, with some minor changes, you could test for a certain number of table rows to be displayed:
The common tips page in the vue-test-utils documentation offers some more suggestions and I highly recommend reading it now. Like almost all Vue related documentation, it’s excellent.
The next problem that most people encounter is integrating a linter within their tests. This is relatively easy to do, but there are two main approaches. One is to have a
.eslintrc file or similar within the test directory, and the other is to use overrides within your main eslint config file. I prefer the first, as it is easier to set up, but it also makes your rules harder to reason about as they do not come from one particular source. An example
.eslintrc file is shown below:
This file serves one purpose: to tell eslint about global variables that it should not mark as
undefined. You can also override linting rules here. If you do not want to put all of your test related files in a single folder (for example if you want to keep
.js files and their associated tests together, which is especially useful for testing vuex actions), you can apply rules based on filename in ESLint. Here is a little snippet to add to your main
.eslintrc file, but you can find out more in the ESLint docs.
The next question that a lot of people ask, ‘What should I test?’, is not easy to answer, and I won’t attempt to do it here. I’ve listed some further reading that you may find useful below. However, if you already know, here are some patterns that you may find useful.
Testing Rendered HTML
A quick way to test rendered HTML is snapshot testing. This simply serializes a mounted component for comparison later, guarding against unexpected UI changes. First, we run
yarn add jest-serializer-vue, and then add a single line to our
package.json in the
Now, we can create a snapshot test very simply:
This just serializes the HTML that is rendered by the component for comparison later. If we actually do change the UI, we can update our snapshots by running
yarn test -u. The resulting snapshot files should always be checked in to version control.
Methods are a perfect candidate for unit testing: when written properly, they depend only on their explicit inputs, and they should return a predictable output based on them. Once you’ve mounted your component, and assigned it to a variable (I have used idiomatic name
wrapper), accessing methods is as simple as calling
wrapper.vm.someMethod(arg1, arg2). You can then test your methods like this:
Obviously, this example is trivial; unit testing methods is only worthwhile if they are complicated enough that they are difficult to refactor anyway.
Testing Computed Properties
Computed properties are more difficult to test, as they rely on some kind of implicit input (either the context they are called in, i.e.
this, or some other state in a broader scope than the function). They should, however, be pure functions, so it’s easy to test their output. We can set data in a Vue component using the
wrapper.setData, or the
wrapper.setProps methods. Both of these initialize the mounted component with data which you can then use to test computed properties.
setProps is being deprecated as I write this, so the correct pattern is as follows:
Advanced Bonus Mega Fun
The component we have been testing so far has been very simple: it does not interact with a vuex store, isn’t aware of routing, doesn’t depend on any external libraries, and doesn’t rely on any browser APIs which aren’t provided by Node. Some or all of these things may need to be mocked in a more complex component, or in a router view.
Mocking the $route object
One of the easiest things to mock is the router, or more specifically, the route object that a Vue instance has access to. For example, if a method or computed property accesses
this.$route.path, we can create an object with this property and feed it into the options argument of the mount function provided by vue-test-utils.
Mocking Browser APIs
Browser APIs must also be mocked relatively often. If a component relies on
localStorage, we need to mock that. At 3YOURMIND, we achieve this using a test helper function to provide stubbed functionality to ensure tests run.
You can now just import this helper in any test which requires it. In this case, the tests don’t actually test the component’s usage of
localStorage. Without this mock, however, the component would not mount for testing and therefore its most basic properties must be stubbed. This kind of function could be extended to provide arbitrary functionality to help you test your components.
Mocking a Vuex store
Mocking the store is usually the most time-consuming part of testing a Vue component. However, with a well-designed store, writing factory functions to make mocking the store less painful is not difficult. For a good primer on large-scale Vue app design, I recommend my colleague Kevin’s Medium article. What I’ve tried to do with the following tests, is to match the structure of the real store exactly in the mocks. Below is a naive implementation of a mocked store in a test; later on, I’ll suggest an alternative and list its benefits.
This test passes, but the test looks a little verbose. If we were accessing three or four modules from a component (or more likely a view which has not been refactored into smaller pieces yet), this would quickly become unmanageable, and our test files would become huge. Instead of mocking the store structure in the
CoolCardNaive.test.js file, let’s abstract it away and move it into a utility which we can import into all of our tests. Another note: we’ve included a new
beforeEach function here. This resets the store before each test to make sure that they are independent of each other. This is an important quality when unit testing.
Factory Functions for mocking the store
Assuming a similar structure to the one that Kevin suggested, here is a very minimal example factory function which will spit out all the mocked getters you need to mount an arbitrarily complex view or component:
You can use this in your tests like so:
Basically, you just need to define mock values for any getters that the component you are testing relies on. It’s completely reusable, as it any getters which are not mocked in the function call return
null and don’t affect the component being tested. Any actions which are needed are just mocked with Jest’s
jest.fn()mock function, which is all that is necessary, as here we are testing the component, and not our actions. Another advantage is that as our application grows, and more modules are introduced, each test only needs one or two extra lines, instead of mocking the shape of a whole module every time.
The downside, of course, is the time spent to create the store factory function in the first place. For a medium-sized app with 7 modules that I am working on at the moment, it took about half an hour, which I feel is worth it.
Bonus Round: Test Structure
The only other thing I would like to mention when it comes to the structure is naming conventions. We try to keep everything as consistent as possible; for example, if we have a component named
SomeComponent.vue, its test will always be named
SomeComponent.test.js. It’s little details like this make onboarding new developers as painless as possible, which is a win for everyone.
Bonus Round 2: Nodelectric Boogavue
Set this as an alias in your shell now, and it will save you hours in the future:
node --inspect-brk node_modules/.bin/jest --runInBand
You can now throw a debugger statement anywhere in your tests, run this command and go to
chrome://inspect in chrome to open a the devtools and debug your tests.
Testing isn’t everyone’s favourite thing to do at work, but thanks to vue-test-utils it can be relatively painless. To finish off, I’d like to recommend some further reading to cover things I didn’t have enough space (or knowledge) to explore properly.
We haven’t covered testing Vuex actions here, as I’ve concentrated on components. Lachlan Miller has written an excellent article about this, which I recommend reading as soon as you’re done here.
This blog post was written in cooperation with the my employer 3YOURMIND, who are looking for developers in Berlin. We are an established startup in the 3D printing space with a growing team of friendly and knowledgeable developers. We use Vue.js, Django REST Framework, Spring, Docker and many more cutting-edge frameworks and technologies. You can find openings here.