Every developer knows that they should be writing tests, but in reality, many of us don’t do it at all or we aren’t doing enough of it. The problem isn’t the lack of tools, it’s that we don’t have a clear understanding of what these tools are meant to do.
If I handed you a hammer in the middle of a grassy field you would be pretty lost on what your next step should be. If I handed you that same hammer in that same field with wood, nails, and blueprints for a shed, you could probably figure out what comes next. In this article, I am going to give you a beginner’s blueprint for what you should be testing in your Vue.js applications as well as the tools you will need to do so.
What you’ll learn
In this article you will learn:
- Why are we writing tests to begin with? What are the goals of testing?
- How to Identify what you should (and should NOT) be testing
- What are the different tools used to write these tests
Goals of writing tests
Testing Instills Confidence
The first goal of testing is too instill some confidence when you are working in a new or existing code base. If you’re new to a repository, seeing a suite of tests is like having an experienced developer sitting right next to you and watching you code, making sure you are staying within the proper lanes of what the code ought to do.
With these tests in place, you can feel confident about adding new functionality or changing existing code. When integrating your code into the larger system, you can just run the whole suite of tests and rest assured that that you didn’t break anything.
Code Quality
When you write your components with testing in mind you create isolated, reusable components. If you start writing tests for your components and you notice they aren’t easy to test, this is a clear sign that you can probably refactor your components, which might just make them better at the end of the day.
Writing tests means creating less bugs in your code, which leads to higher code quality.
Documentation
The last goal of testing is that it can end up producing good documentation for your development team. When I am new to a code base and I don’t quite understand something, I often look to the tests for guidance. The tests will usually give me an indication of developer intention because in most cases they are the ones that wrote the feature and the tests. This also helps me understand edge cases because that is something you would usually test for.
Identifying what to test
So we understand the value of testing, but what should we be testing in our applications? The answer is actually quite simple in Vue.js: components. Since a Vue app is merely a puzzle of interlocking components, we should be testing their behavior and how they interact with each other.
Testing the Public Interface
So we know we should be testing at the component level, but what part of the component should we be testing? A good place to start is what is referred to as the public interface. If you were writing documentation that described what the component does, these are the things that you should be testing. You can think of the public interface as the different inputs a component might take and what the outputs might be.
Inputs
- Data: the component’s data
- Props: the component’s props
- User Interaction: user clicks a button
- Lifecycle Methods:
mounted()
,created()
, etc. - Store: state management data
- Route Params: data in the route
Outputs
- Rendered Output (Data): what is rendered to the DOM
- Events: component emits an event
- Route Changes: when the route changes
- Store: updates to the state
- Connection with children: changes in child components
By focusing on the public interface, you avoid focusing on the internal business logic, getting bogged down by worrying about how every single line of code works. It may seem counterintuitive, but the goal of unit testing is purely to ensure your components are producing the expected results. We aren’t concerned here about how it arrived at that result.
Now that we know what we should be testing for, let’s look at a couple basic examples and try to identify what we might test in each.
Example 1: AppHeader Component
In this example, we have a component that will display a logout button if the loggedIn
property is true
. Before we move on, can you identify the component’s inputs and outputs?
📄 AppHeader.vue
<template>
<div>
<button class="btn" v-show="loggedIn">Logout</button>
</div>
</template>
<script>
export default {
data() {
return {
loggedIn: false
}
}
}
</script>
Inputs
- Data (
loggedIn
) - This data property determines if the button shows or not, so this is an input that we should be testing
Outputs
- Rendered Output (
button
) - Based on the inputs, is our button displayed in the DOM
This basic example gives us some basic practice for identifying inputs and outputs, but let’s now look at a component that takes in props and contains some internal business logic.
Example 2: Random Number Generator Component
This component will generate a random number between the min
and max
props that have some default values. Can you determine the inputs and outputs of this component?
📄 RandomNumberGenerator.vue
<template>
<div>
<span class="number">{{ randomNumber }}</span>
<button v-on:click="generateRandomNumber">Generate Random Number</button>
</div>
</template>
<script>
export default {
props: {
min: {
type: Number,
default: 1
},
max: {
type: Number,
default: 10
}
},
data() {
return {
randomNumber: 0
}
},
methods: {
generateRandomNumber() {
this.randomNumber =
Math.floor(Math.random() * (this.max - this.min + 1)) + this.min
}
}
}
</script>
Inputs
- Data:
randomNumber
- Props:
min
&max
- User Interaction: Generate Random Number
button
Outputs
- Rendered Output (DOM): is the number displayed on the screen between
min
andmax
As we’ve seen, the first part of writing effective tests is being able to identify what parts of the component to test. Now that we have some experience with that, we need to understand what we should not be testing.
What NOT to test
Understanding what doesn’t need to be tested is an important part of the testing story that many developers don’t think about, and in turn it costs them a lot of time that could be spent elsewhere.
Implementation Details
When testing, we don’t need to fuss over how certain things work, just that they do work. Let’s go back to our random number generator component. We know that we want to test that we get a random number that falls between our min
and max
values. Here is the function that generates that random number for us.
📄 RandomNumberGenerator.vue
generateRandomNumber() {
this.randomNumber = Math.floor(Math.random() * (this.max - this.min + 1) ) + this.min;
}
To test this component, all we need to know is that it generates a random number between the min
and max
. We aren’t going to set up a test that calls this function and makes sure it behaves in a certain way. We don’t care about the internals here on how Math.floor()
works, just that it produced the output we were expecting. This way, we can always come back later and replace the implementation logic, with a third-party library for generating random numbers for example.
Testing the Framework Itself
Developers often try to test too much, including the inner workings of the framework itself. But the framework authors already have tests set up to do that. For example, Vue does an amazing job of watching for data to change and updating the DOM as needed. If we want to test some conditional logic on how things get rendered, that is ok, but checking to see if a data property got rendered to the template isn’t the most efficient use of our time.
Another framework feature that is tested often is prop validation. We can assume that if we set up a prop to be a number that it will throw an error if we try passing in a string. Therefore, these types of tests can be avoided.
Third party libraries
When I say third party libraries, I mean both libraries that integrate with Vue like Vue Router and Vuex as well as libraries outside of the Vue ecosystem installed via NPM, such as Axios. Just like the Vue framework itself, these libraries already have their own tests so we don’t need to test their internals.
Let’s take for example a CRUD app that saves new users. When a new user is saved,Vue Router pushes the visitor to the user listing page. There is no need to test if the Vue Router push method does its job correctly, because we know it does. These are the types of things that will bog us down if we worry about them unnecessarily.
Conclusion
In this article we took an important first step in writing effective tests: identifying what you should and shouldn’t be testing in components. With this approach, we can focus our time wisely on testing the pieces that need to be tested. In part 2 of this series, we’ll take this knowledge and apply it in a more practical example.