Unit vs E2E Testing for Vue.js
Anthony Gore | April 1st, 2019 | 4 min read
Writing tests when developing Vue.js apps can save you a lot of time which would be otherwise spent fixing bugs. The bigger and more complex your app gets, the truer this becomes.
There are two types of tests that are commonly performed for web applications: unit tests and end-to-end (E2E) tests.
What's the difference? Do you need both?
Let's explore.
Unit tests
The idea of a "unit" in testing, is to break down the code into small, easily testable parts. Usually, the unit is a single function, but can also be a class or even a complex algorithm.
A crucial concept of unit testing is that a given input of the function should always result in the same output.
For example, if we had a function that added two numbers called add
we could write a unit test to ensure that a particular pair of numbers we provided as arguments would always return the output we expect.
add.spec.js
// Function we want to test
const add = (x, y) => x + y;
// Unit test
test("should add two numbers", () => {
const result = add(2, 3);
expect(result).toBe(5);
});
Any time we run that test and it doesn't equal 5, we can conclude a bug has entered our code.
Component tests
In most Vue.js applications functions don't really represent the atomic makeup of the app. Sure, we can unit test our methods, but what we also really care about is the HTML that's generated.
For this reason, the unit in a Vue.js app test is a component rather than a function.
How do we test components? Let's take this one as an example:
displayGreeting.js
export default {
template: `<div>Hello, {{ name }}</div>`,
props: ['name']
};
As previously stated, a unit test must, for a given input (in this case, a prop), return a consistent output (in this case, text content).
Using a library like Vue Test Utils, we can mount a Vue component in memory and create a "wrapper" object. We can then query the wrapper to make assertions about the rendered HTML.
displayGreeting.spec.js
import displayGreeting from "./displayGreeting.js";
test("displays message", () => {
const name = "Michael";
const wrapper = mount(displayGreeting, { propsData: { name } });
expect(wrapper.text()).toBe(`Hello, ${name}`);
});
Snapshot tests
In the above example, we used the wrapper.text()
to query for the text in the component output.
In most components, though, testing the veracity of the output will require more than one snippet of text. We often want to ensure that a variety of elements are present.
Maybe it'd be easier to test the entire HTML output of the component?
Another kind of component unit test is a snapshot test where you do exactly that.
How it works is that you generate the output of the component once and write it to a text file. For example:
displayGreeting.spec.js.snap
exports[`renders correctly 1`] = `<div>Hello, Michael</div>`;
Now, any time the tests run, if the rendered output of the component differs from what's in the file, the test will fail.
Snapshots are a blunt instrument, but they are good for testing components which display a lot of HTML.
E2E tests
E2E (end-to-end) testing is a type of functional test. Unlike a unit test, you're not breaking the application down into smaller parts in order to test it - you're testing the entire application.
E2E tests interact with your app just like a real user would. For example, you may write an E2E test which:
- Loads your site
- Clicks on the "Sign up" link
- Provides some valid details to the inputs in the registration form
- Click the "Register button".
This test should pass if an authentication token has been stored in the cookies and the app redirected to the profile page.
Tools
E2E tests are made on top of a browser automation driver like Selenium that provides an API to drive the browser.
An E2E testing framework like Cypress or Nightwatch will then provide a way for you to script your E2E tests for the browser automation driver.
The following code is what you might use in Nightwatch to perform the test described in the section above. You can probably tell what it does even if you've never used Nightwatch.
register.spec.js
"register user": browser => {
// Navigate to register page
browser.page.register()
.navigate()
.waitForElementPresent(form.selector, 5000);
// Fill out the form
register.section.form
.setValue("@nameInput", "Anthony")
.setValue("@emailInput", "anthony@test.com")
.setValue("@passwordInput", "test1234")
.click("@submitButton");
// Make assertions
browser
.assert.urlEquals(profile.url)
.getCookie(name, (result) => {
this.assert.equals(result.name, 'auth');
}
});
}
Unit and E2E comparison
Unit pros:
- Tests run fast
- Test are precise and allow you to identify exact problems
Unit cons:
- Time-consuming to write tests for every aspect of your app
- Despite unit tests passing, the whole application may still not work
E2E pros:
- Can implicitly test many things at once
- E2E tests assure you that you have a working system
E2E cons:
- Slow to run - will often take 5 or 10 mins to run for one site
- Brittle - an inconsequential change, like changing a class, can bring down your entire E2E suite
- Tests can't pinpoint the cause of failure
Verdict
In my opinion, a combination of both unit and E2E tests is the best approach. The cons of one type can be mostly nullified by the pros of the other.
For example, E2E test won't tell you the root cause of failure, but unit tests will, while unit tests won't tell you if the whole application is working or not, while E2E tests will.
Using these test types together will give you a lot of confidence in your application, allowing you to add features or refactor without fear of collapse.
The general strategy for combining unit and E2E tests for a Vue.js app is this:
- Write unit tests for all your components, including error states. Run these before you make git commits.
- Write E2E tests for the key use cases of your site e.g. registeration, add to cart, etc. Run these before merging to master.
If you want more details on the right mix of tests, there a plenty of good blog posts like the classic Write tests. Not too many. Mostly integration. by Kent C. Dodds.
Bonus: testing tools
So you're ready to start testing, what tool can you use?
For frontend unit testing, the best tool right now is Jest. It has many useful features, for example, allowing you to compile TypeScript and modern JS before the tests run.
You can use Jest in conjunction with Vue Test Utils which allows you to mount and query Vue components.
For E2E, the state-of-the-art tool right now is Cypress. Another more basic tool that also works well is Nightwatch.
The good news is that it's easy to add all of these tools to a Vue application with Vue CLI 3.
Finally, it's a good idea to use a continuous integration tool like Travis or Circle CI that will run your tests in the cloud (especially good for time-consuming E2E tests) and deploy your code conditional on all your tests passing.
Happy testing!
About Anthony Gore
If you enjoyed this article, show your support by buying me a coffee. You might also enjoy taking one of my online courses!
Click to load comments...