Using Vue Test Utils With Jest - Vue 3
In our previous posts we have talked a little informally about testing Vue 3 applications using Jest and vue-test-utils. In this post we will dig a little deeper into the specifics of how vue-test-utils works.
vue-test-utils is a set of tools which help us to write tests for Vue components without having to write those tests as End-2-End tests to be executed against a running web site. In support of those goal, a few key skills are required to make the most of using those tools:
- The Document Object Model (DOM)
- CSS Selectors
- DOM Events
- Virtual DOM
The Document Object Model (DOM)
The Document Object Model (or DOM for short) is an API that treats HTML as a tree structure wherein each node is an object representing part of the document. The DOM model represents a document with a logical tree. Each branch of the tree ends in a node, and each node contains objects. DOM methods allow programmatic access to the tree; with them you can change the document's structure, style or content. Nodes can have event handlers attached to them. Once an event is triggered, the event handlers get executed.
Let's try to understand how a simple HTML page is represented in the DOM.
The document object is exposed to JavaScript which is running in the browser. That object has a number of Built-In Functions which can help you to navigate and/or manipulate the DOM. Using one of those built-in functions, document.getElementsByTagName(), allows us to get a reference to the button element in our HTML document. You'll see that we attached a JavaScript function to that element using the onClick event handler for the button. And the function it calls modifies the content of the Button element as shown in the JSFiddle above.
See if you can modify the JavaScript function above to write the message "Clicked
CSS Selectors
This is where CSS selectors come in handy! One of the other built-in functions of the document object is querySelector. Query selector uses a concept called CSS Selectors. With CSS selectors, we can QUERY the DOM for elements which match certain attibutes. For example, our scenario above might be implemented like so:
In this example, we use querySelector to find a div element with a CSS class applied called another_css_class. We could try it with other selector options. For example, if we wanted to update the content of the button we might use something like:
Now, we have used querySelector to get a reference to the button object which has a type of button.
DOM Events
Finally, let's talk about triggering DOM event from code. We know that clicking on our button above will cause the clicked event to be fired and thus result in our handler method being executed. When we're talking about writing automated tests though, we need to be able to trigger those events programmatically. Let's look at an example of how we might accomplish that:
Virtual DOM
We've learned a little about the DOM now, and understand a little about CSS selectors and how we can use them to find the elements we want to manipulate. Now, let me tell you why manipulating the DOM directly is a BAD IDEA! The DOM, when running in a browser, can be large and complex. Additionally, whenever you modify the DOM from JavaScript, it CAN cause the entire page to be rendered again, or even with small changes it can cause a "paint" event which is how pixes are composited to the screen. Anytime the browser has to render something to the screen, it can be a relatively expensive operation. If you are modifying lots of things quickly in a reactive manner, that can lead to some bad things: screen flickering, high resource usage, hung browsers, etc... Another concern is modifying styles dynamically. Because style sheets "cascade", changes to the styles of pages can cause the browser to try to recalculate the entire rendered page. Changing styles too much leads to a condition known as "thrashing".
Vue JS, and several other modern front-end frameworks, try to avoid most of these problems by using something called a "Virtual DOM". Since the DOM is just a JavaScript object, we can create a Virtual DOM also as a JavaScript object. It would have all of the same methods, attributes, elements, values, etc... The major difference is that our Virtual DOM is not tied to the browser's rendering engine and therefore doesn't lead to those bad things mentioned earlier. Instead, your Virtual DOM is only applied to the ACTUAL DOM after each "Tick". You can read more about "ticks" and how changes are tracked in Vue HERE. Suffice it to say that Vue "batches" together changes to the Virtual DOM and applies them in a more conservative fashion which helps us to avoid a lot of the issues identified. The other kind of handy thing about the Virtual DOM is that it lets you isolate your tests from an actual browser. You can run them with just Node.js.
Leveraging CSS Selectors In Vue Test Utils
Now that we know what CSS selectors are and we know where to go to learn more about the sorts of queries we can write, let's apply that to Vue Testing Utils. Inside of Vue Test Utils, it starts up a Virtual DOM, which is similar to how Vue works in a browser. Using that Virtual DOM, Vue Test Utils will mount a page or view or component into that Virtual DOM and allow us to manipulate it in the same way we can manipulate the real DOM. For example:
In the example above, we use the shallowMount method from Vue Test Utils to mount a single Vue component. We then use mounted component reference and a CSS selector (expect(wrapper.findAll('li')).toHaveLength(items.length)) to create an assertion with Jest so that we can confirm that the List is the length that we expect it to be.
This last example shows how we can use Vue Test Utils in conjunction with CSS selectors to trigger DOM events. The call to wrapper.find("#toggle-message') means that we are searching for an element with the id of toggle-message. Once we have a reference to that element, we trigger a click event on that element. We then use Jest's expect methods to make assertions about the state of the DOM after that event is handled.
One thing to keep in mind though is when you are working with Asynchronous methods (callbacks, promises, or async/await). If an operation is async, the tests in Vue Test Utils may require you to wait for the next "Tick" in Vue. In order to do this, you can use the nextTick method in Vue Test Util's API as shown below:
You'll see in that example that the test method is prefixed with async. After the button click, we tell Vue Test Utils to await wrapper.nextTick() in order to let the asynchronous operations to complete. This is useful for when you are using an asynchronous library like Axios to make REST API calls to respond to user interactions.
Hopefully, that helps you to better understand testing in Vue.js with Vue Test Utils. I'd love to hear your feedback below!
Comments