Get your team started on a custom learning journey today!
Our Boulder, CO-based learning experts are ready to help!
Get your team started on a custom learning journey today!
Our Boulder, CO-based learning experts are ready to help!
Follow us on LinkedIn for our latest data and tips!
If you’re wise, before you decide to use a framework, you check to make sure you’ll be able to adequately unit test your application if that framework is in place. So, before jumping onto the Vue bandwagon, this question must be answered: can you unit test a Vue application without going through more trouble than it’s worth? Well, let’s take a look at how to test Vue components and you can come to your own conclusion.
There are several unit testing library/framework options, but if you’re looking for a recommendation, then use the Vue CLI to scaffold out your Vue project, using either the “webpack”, “browserify”, or “pwa” template, and then answer “Y” to the “Setup unit tests with Karma + X?” (X is Jasmine when you use the browserify template and Mocha for the other two templates). Then it will pretty much all be set up for you. Many people would prefer to make their own decisions about what frameworks to use and how they set up the project to do testing. That’s not a bad thing, but sometimes it’s better to let someone/something else make that decision for you rather than spending precious developer time on researching your own choice.
In this case, Karma allows you to run all of your tests in multiple browsers at once or just PhantomJS as the default. It also integrates with webpack so you know your components are being compiled the same way they are when the application is built. This is especially useful when using Single File Components (aka .vue
files), which can be tricky to compile otherwise. Mocha and Jasmine are both quite popular testing libraries, so you can’t go wrong with either one.
If, however, you really want to consider all of your options, then Jest may be a good solution. It’s touted as being very fast and the snapshots feature can make testing the final HTML output extremely simple. If you’re using .vue
files, then you’ll need a pre-processor to compile them. This sadly doesn’t work well with CSS Modules, but there are workarounds that work to an extent.
As another consideration, you may want to have code coverage reporting. In that case Jest is a good option because it’s built in, but Vue CLI will also include code coverage if you decide to do unit testing. In any case, there are several code coverage libraries out there that can integrate with just about any unit testing framework.
Honestly, any framework should work as long as you either use plain JavaScript components or can find a way to compile the .vue
files to JavaScript. There are too many options to be able to go into any kind of detail regarding setup, except to say that Vue CLI can do it for you if you’re OK with their choices.
For these examples, it’s assumed that you’re using the unit testing setup that Vue CLI scaffolded out for you with the “webpack” template. For the most part, the concepts carry over to the other testing libraries. If you’re using something else, you should hopefully be able to follow along anyway.
To start with, let’s create a relatively simple component that’s pretty useless but will allow us to learn how to test multiple aspects of Vue components. We’ll call it Foo.vue
:
{{ msg }}
Some Text
Now let’s create our first unit test file for this file, which should be located in the test/unit/specs
folder if you’re using the same scaffolding template. We’ll call it Foo.spec.js
and just write out the very basic things to set up:
import Vue from 'vue' import Foo from '@/components/Foo' // @ is configured to be our main src directory decribe('Foo.vue', () => { // Our tests will go here }
The first and simplest thing to test is the static functions on your components. In this case, we can test Foo.data
and Foo.methods.giveZero
, which should work without instantiating or mounting the component. Foo.computed.msg
and Foo.watch.who
both reference this
, so they’ll need to be tested on an instance of Foo
.
it('should have correct `data`', () => { expect(typeof Foo.data).to.equal('function') const data = Foo.data() expect(data.who).to.equal('world') expect(data.updates).to.equal(0) }) it('should return 0 from `giveZero`', () => { expect(typeof Foo.methods.giveZero).to.equal('function') expect(Foo.methods.giveZero()).to.equal(0) })
All of these tests should pass, so let’s take a look at testing a component instance since you can’t get very far simply by testing the static component definition.
To do this we need to use Vue to mount the component, but it won’t be mounted to any element in the DOM; it will just render it in memory. There are two ways to do this:
// Mount method 1
const Constructor = Vue.extend(Foo)
const vm1 = new Constructor().$mount()
// Mount method 2
const vm2 = new Vue(Foo).$mount()
While the first method is more verbose, it has the advantage of allowing you to pass in options for props. For example:
const Constructor = Vue.extend(Foo)
const vm1 = new Constructor({
propsData: {
someProp: 'custom value'
}
}).$mount()
That’s equivalent to the following code inside a template:
You’ll likely end up mounting your components quite a bit during your testing, so it may be wise to create a helper function to do it and put it into a module that you can import into all of your tests.
function mount(component, options) {
const Constructor = Vue.extend(component)
return new Constructor(options).$mount()
}
Now let’s write a couple tests to see how to use mount
:
it('someProp defaults to "default value"', () => {
const foo = mount(Foo)
expect(foo.someProp).to.equal('default value')
})
it('someProp can be set', () => {
const foo = mount(Foo, { propsData: { someProp: 'custom value' } })
expect(foo.someProp).to.equal('custom value')
})
Note, just like when working inside a component, once an instance is created, all props, data, and computed properties can be accessed directly off the root of the instance. There’s no need to find a computed property under foo.computed.msg
or anything like that.
it('computed property is correct', () => {
const foo = mount(Foo)
expect(foo.msg).to.equal('Hello world')
})
Arguably, the most important thing that a component can do is render the correct DOM, so it’s probably important to test if the DOM was rendered correctly. We can access the DOM through the $el
built-in property on our component instance. Through this, we can check anything we want, such as whether or not the correct text was rendered in a certain element. For example:
it('render proper DOM', () => {
const foo = mount(Foo, { propsData: { someProp: 'custom value' } })
expect(foo.$el.querySelector('h1').textContent).to.equal('Hello world')
expect(foo.$el.querySelector('p').textContent).to.equal('custom value')
})
One of Vue’s greatest strengths is its ability to automatically propagate changes wherever they need to go when one change is made. For example, a computed property will be updated automatically when one of its dependent properties changes. Also, the component will re-render with new data when a change happens. So we need to be able to test the reactive outcomes of changes we make as well.
When testing computed properties, it’s a simple matter of checking to see if the computed property’s value reflects what it should be once a dependency changes:
it('computed property updates correctly', () => {
const foo = mount(Foo)
foo.who = 'universe'
expect(foo.msg).to.equal('Hello universe')
})
Checking that those updates propagate to the rendered DOM is a bit more trouble, though. If we make a change to foo.who
like we just did and then check the DOM, it’ll give us the same DOM output that it had when the foo
was initialized, so the following tests will fail:
it('render proper DOM on changes', () => {
const foo = mount(Foo, { propsData: { someProp: 'custom value' } })
foo.who = 'universe'
foo.someProp = 'really custom value'
expect(foo.$el.querySelector('h1').textContent).to.equal('Hello universe') // FAIL!!
expect(foo.$el.querySelector('p').textContent).to.equal('really custom value') // FAIL!!
})
This happens because Vue wisely renders asynchronously. This prevents it from blocking the JS thread, but it also allows the entire chain of reactive changes to finish taking place before it renders, so it doesn’t end up rendering multiple times. We can use Vue.nextTick
and pass it a callback that will run once it finishes the next rendering cycle, which will allow us to test the DOM.
To allow us to test asynchronous functionality, we need to use a done
parameter (you can use any name you want but “done” is pretty common) in the callback to it
so we can tell our test runner when the test finishes.
it('render proper DOM on changes', (done) => { // {
expect(foo.$el.querySelector('h1').textContent).to.equal('Hello universe')
expect(foo.$el.querySelector('p').textContent).to.equal('really custom value')
done() // Call `done` to say we're done
})
})
Alternatively, you can return a Promise if your test runner supports it (I believe all latest releases of test runners do). And if you’re using Promises, you make it easier to read by using async functions with async
and await
. First, we’ll need to convert nextTick
to use promises though by creating a helper function:
function nextVueTick() {
return new Promise((res) => Vue.nextTick(res))
}
Now we can convert our previous test to look like this:
it('render proper DOM on changes', async () => { //
If you're using Jest or another testing framework that supports snapshots, the simplest way to test the DOM is simply to use foo.$el.outerHTML
and compare it to a snapshot. That ensures you're able to test the entire rendered DOM rather than picking and choosing bits to check.
We can also use spies to make sure that certain functions are called:
it('call `giveZero` on click', () => {
sinon.spy(Foo.methods, 'giveZero') //
Note that we create the spy on the component definition rather than on the instance in this case. This is because when the instance is created, the click handler will have a reference to the method that was already defined. If we override it with a spy on the instance, it will never be called because the spy won't get registered as the click handler. Also, note that we're checking Foo.methods.giveZero.called
rather than foo.giveZero.called
. This is because when an instance is created, Vue will wrap giveZero
, so foo.giveZero.called
is undefined. Calling Foo.methods.giveZero.restore()
will return giveZero
to its original function and will prevent other tests from using the spy.
We can also use spies to check on watch
functions, which are similar to DOM rendering in that they are asynchronous (there are ways to make them synchronous, but generally you won't be doing that). This means we'll need to bring back our nextVueTick
helper:
it('watcher triggered when `who` changes', async () => {
sinon.spy(Foo.watch, 'who') //
That's pretty much it. As you can see, just about everything about Vue components is testable without requiring too much finagling, so Vue's testability should not prevent anyone from making it their choice of web UI framework.
If that's not simple enough for you, then maybe you should check out one of the following libraries that are designed to simplify some of the aspects around testing Vue components.
Avoriaz: This library simplifies mounting, along with being able to pass more options in during mounting. It wraps components to help make DOM traversal and event triggering simpler. Avoriaz can also do shallow rendering so the rendered DOM isn't dependent on child components. With this in place, you won't need to update tests for parent components when the child component is changed. Finally, it offers a method to synchronously re-render, but asynchronous watchers still need to use Vue.nextTick
.
vue-test-utils: This library is officially supported by the Vue development team, but currently it's only in beta. It took heavy inspiration from Avoriaz, but is looking to expand its capabilities as well as make improvements to the API.
Customized Technical Learning Solutions to Help Attract and Retain Talented Developers
Let DI help you design solutions to onboard, upskill or reskill your software development organization. Fully customized. 100% guaranteed.
DevelopIntelligence leads technical and software development learning programs for Fortune 500 companies. We provide learning solutions for hundreds of thousands of engineers for over 250 global brands.
“I appreciated the instructor’s technique of writing live code examples rather than using fixed slide decks to present the material.”
VMwareThank you for everyone who joined us this past year to hear about our proven methods of attracting and retaining tech talent.
© 2013 - 2020 DevelopIntelligence LLC - Privacy Policy
Let's review your current tech training programs and we'll help you baseline your success against some of our big industry partners. In this 30-minute meeting, we'll share our data/insights on what's working and what's not.
Training Journal sat down with our CEO for his thoughts on what’s working, and what’s not working.