Description: C:\Users\Chris\Desktop\Manning\Images\cover images\vuejs in act.jpg

From Testing Vue.js Applications by Edd Yerburgh

Part 1 covers:

  • Testing native DOM events
  • Testing custom Vue events


Save 37% off Testing Vue.js Applications. Just enter code fccyerburgh into the discount code box at checkout at manning.com.


If money makes the world go round, then events make web apps go round. Without events websites are static HTML pages. With events, websites become powerful applications that respond to user interactions to create something truly beautiful.

In Vue applications there are two types of events that you’ll encounter: native DOM events and Vue custom events. In this article you’ll learn about both types of events and how to test them.

Events are an important part of most Vue applications, and before you can call yourself a testing master, you should know how to test them. In this article you’ll write a popup email subscribe form. You know, the kind of form that appears when your mouse leaves a page and asks you to subscribe for more content (figure 1).


Figure 1. The finished popup email subscribe form that you’ll create in this article


The sign-up form is made from three components—a Modal component, a Form component, and an App component. Some code is added in the starter branch of the project.

INFO To follow this article, you need to clone the chapter-5 project and check out the starter branch. This article uses a project from the book to learn how to test events in Vue apps. To get started, you need to download the project using Git clone:

  
 git clone git@github.com:eddyerburgh/vue-email-signup-form-app.git
  

Or with https:

  
 git clone https://github.com/eddyerburgh/vue-email-signup-form-app.git
  

You need to change into the Git repository:

  
 cd vue-email-signup-form-app
  

Then change to the starter branch with Git checkout:

  
 git co starter
  

Once you’ve got that set up, you’re ready to follow along!

The first section of this article is about testing native DOM events. To learn how to test DOM events, you’ll write tests for the Modal component. In the second section, you’ll refactor the Modal component to use Vue custom events.

The third section of this article focuses on testing input forms. Input forms have some nuances that can be tricky when you first encounter them. You’ll write tests for the Form component to see how to test text-input elements and radio buttons.

The final section of this article is about the limitations of jsdom. You’re going to find that jsdom can make unit testing forms difficult. Once you have cloned the project, and run npm install, you can begin. The first topic you’ll cover is testing native DOM events.

Testing native DOM events

Native DOM events are how the browser alerts JavaScript code that something interesting has happened. Lots of DOM events exist. For example, clicking an element triggers a click event, hovering the cursor over an element triggers a mouseenter event, and submitting a form triggers a submit event.

INFO If you aren’t familiar with native DOM events, MDN has a great primer you can read at https://developer.mozilla.org/docs/Learn/JavaScript/Building_blocks/Events

In Vue apps you can use event listeners to respond to events with handler functions. Each time an event is triggered on the element, the handler function is called.

For example, to increment a count value when a button is clicked, you use a v-on directive with a click argument:

  
 <button v-on:click="count++">Add 1</button>
  

Vue provides a shorthand notation for the v-on directive, which I’ll use in this article. The following is the same example using the @ shorthand:

  
 <button @click="count++">Add 1</button>s
  

Native DOM events are often the input for a component unit test. For example, imagine you wanted to test whether clicking a button hides an element. In the test, you’d simulate a click event on the button element and then assert that the element is removed from the DOM.

In this section you’re going to add test that a Modal component calls an onClose prop, when the close button is clicked. To test that a prop is called when the <button> element is clicked, you need a way to simulate a click event on the button element. You can do this with the Vue Test Utils trigger method.

Using the Vue Test Utils trigger method

In Vue Test Utils, every wrapper has a trigger method that dispatches a synthetic event on the wrapped element.

DEFINITION A synthetic event is an event created in JavaScript. In practice, a synthetic event’s processed the same way as an event dispatched by a browser. The difference is that native events invoke event handlers asynchronously via the JavaScript event loop; synthetic events invoke event handler synchronously.

trigger takes an eventType argument to create the event which is dispatched on the wrapper element. For example, you could simulate a mouseenter event on a <div> by calling trigger on the wrapper of a <div> element (listing 1).

Listing 1 Triggering a mouseenter event

  
 const wrapper = shallowMount(TestComponent)
 wrapper.find('div').trigger('mouseenter')
  

NOTE The trigger method can be used to simulate any DOM event. For example, it could simulate an input event, a keydown event, or a mouseup event.

In your test, you’ll call trigger with a click event type to simulate a click event. You’ll create a mock function and pass it to the component as the onClose prop. Then you’ll simulate a click event on the close button with trigger and assert that the mock was called correctly.

Add the code from listing 2 into the describe block in src/components/__tests__/Modal.spec.js.

Listing 2 Triggering a test by dispatching a DOM event

  
 test('calls onClose when button is clicked', () => {
   const onClose = jest.fn()               ❶ 
   const wrapper = shallowMount(Modal, {   ❷ 
     propsData: {
       onClose
     }
   })
   wrapper.find('button').trigger('click') ❸ 
   expect(onClose).toHaveBeenCalled()      ❹ 
 })
  

❶   Create a mock to pass to the Modal component

❷   Shallow mount the Modal with an onClose prop

❸   Find the button and simulate a click event

❹   Assert that the mock was called

You’ll notice there’s an existing test in the file, which checks that the modal renders the default slot content. This is part of the Modal’s contract: it renders a default slot. You don’t need to change this test.

To make both tests pass, you’ need to update the Modal component to call the onClose prop when the close button’s clicked. Add the code from listing 3 into src/components/Modal.vue.

Listing 3 Calling a prop when button is clicked

  
 <template>
   <div>
     <button
       @click="onClose" ❶ 
     />
     <slot />
   </div>
 </template>
  
 <script>
 export default {
   props: ['onClose']
 }
 </script>
  

❶   Add onClose using v-on directive @ shorthand

Run the tests to make sure they pass: npm run test:unit. Congratulations, you’ve learned how to trigger native DOM events in tests.

Most of the time when you test native DOM events you call trigger with the correct event name, like you did. Sometimes though, your code uses values from the event target. You’ll learn how to write tests that use the event target later in this article in the testing forms section.

Before you learn how to test forms, you’ll learn how to test Vue custom events. If you open src/App.vue, you’ll see that you’re passing a closeModal method as the onClose prop to Modal. The closeModal method sets displayModal to false, and the App component won’t render the Modal component.

You refactor the App to listen to a Vue custom event instead. To do that you need to learn how to test Vue custom events,.

Testing custom Vue events

What’s better than native DOM events? Vue custom events! Vue has its own custom event system, which is useful for communicating to a parent component.

Custom events are emitted by Vue instances. As with DOM events, components can listen for Vue events on child components with the v-on directive:

  
 <my-custom-component @custom-event="logHello" />
  

INFO if you want to read more about Vue custom events, check out the Vue docs. https://vuejs.org/v2/guide/components-custom-events.html

Custom events are useful for communicating from a child component to a parent component. A child component can emit a custom event and a parent component can decide how to respond to the event.

Two parts of the Vue custom event system should be tested—the parent component that listens to a custom event and the component that emits an event:

  1. For components that emit events, the emitted event is the component output.
  2. For parent components that listen to custom events, an emitted event is the component input.

To learn how to test Vue custom events, you’ll refactor the app to use Vue custom events instead of native DOM events in the Modal and App components. You’ll start by rewriting the Modal component to emit a custom Vue event.

Testing that components emit custom events

Vue custom events are emitted by a component instance with the Vue instance $emit method. For example, to emit a close-modal event, you make a call like this from the component:

  
 this.$emit('close-modal')
  

Emitting a custom event is part of a component’s contract. Other components rely on a child component emitting an event, which means it’s important to test that a component emits an event when it’s provided the correct input.

You can test that a component emits an event with the Vue Test Utils emitted method. Calling emitted with an event name returns an array that includes the payload sent in each emitted event.

NOTE You can use emitted to test that an event was called in the correct order or with the correct data. Check out the Vue docs to see other examples of testing with emitted— https://vue-test-utils.vuejs.org/en/api/wrapper/emitted.html

You’ll write a test to check that a close-modal event’s emitted when the close button is clicked. The test simulates a click on the button and asserts that a close-modal event was emitted by the component instance using the emitted method. Open src/components/__tests__/Modal.spec.js. Delete the calls onClose when button is clicked test, and add the test from listing 4.

Listing 4 Testing that a component emits an event

  
 test('emits on-close when button is clicked', () => {
   const wrapper = shallowMount(Modal)
   wrapper.find('button').trigger('click')                   ❶ 
      expect(wrapper.emitted('close-modal')).toHaveLength(1) ❷ 
 })
  

❶   Find wrapper of button element and dispatch a DOM click event

❷   Assert that close-modal was emitted once

Now you can run the tests to make sure the new test fails: npm run test:unit. You can make the test pass by refactoring the Modal component to emit an event when the button is clicked. The $emit call is inline in the template, and you can omit this. Open src/components/Modal.vue and update the button tag to emit a close-modal event on click:

  
 <button @click="$emit('close-modal')" />
  

The component doesn’t receive any props now, and you can delete the entire <script> block in src/components/Modal.vue. The tests pass when you run the command npm run test:unit. Good, you’ve converted the Modal component to emit a custom event.

You can write one more unit test for extra practice. In the project, you’ll see you have a Form component in src/component/Form.vue. This is going to be the form for users to submit their email. You’ll add a test to check the form emits a form-submitted event when the <form> element is submitted. Create a test file— src/components/__tests__/Form.spec.js—and add the code from listing 5.

Listing 5 Testing a Vue custom event’s emitted

  
 import Form from '../Form.vue'
 import { shallowMount } from '@vue/test-utils'
  
 describe('Form.vue', () => {
   test('emits form-submitted when form is submitted', () => {
     const wrapper = shallowMount(Form)
     wrapper.find('button').trigger('submit')                  ❶ 
     expect(wrapper.emitted('form-submitted')).toHaveLength(1) ❷ 
   })
 })
  

❶   Dispatch a submit event on a button element

❷   Assert that the form-submitted custom event was emitted

To make the test pass, you’ll add a submit event listener to the form element. In the event handler, you can emit form-submitted. Copy the code from listing 6 into src/components/Form.vue.

Listing 6 Emitting custom event on form submit

  
 <template>
     <form @submit="onSubmit">      ❶ 
         <button />
     </form>
 </template>
  
 <script>
  
 export default {
   methods: {
     onSubmit () {
       this.$emit('form-submitted') ❷ 
     }
   }
 }
 </script>
  

❶   Add onSubmit submit listener with v-bind

❷   Emit a form-submitted event in the onSubmit method

Run the tests to watch them pass: npm run test:unit. The application is already listening for the form-submitted event in the App (src/App.vue) component. When Form emits a form-submitted event, the App closeModal method fires and removes the Modal from the page (you can check this by running the dev server: npm run serve).

You’ve written tests for the first part of the custom event system, where an emitted event is the output of a component. Now you need to learn how to write tests for components where an emitted event is the input for a component.

Testing components that listen to Vue custom events

If a Vue component emits an event and no component is listening, does it make a sound? I’m not sure, but you could write a test to see!

Like you saw earlier, components can listen to custom events emitted by their child components and run some code in response:

  
 <modal @close-modal="closeModal" />
  

You refactored the Modal component to emit a close-modal event. That changed the components existing contract, which broke the app. you need to update the App component to listen to the close-modal event, and hide the Modal when the close-modal event is emitted. You’ll write a test to make sure that it does.

To test that a component responds correctly to an emitted event, you can emit the event from the child component by finding the instance wrapper and accessing the vm property:

  
 wrapper.find(Modal).vm.$emit('close-modal')
  

In the App component test you’ll emit a close-modal event from the Modal and check that the Modal component is removed from the rendered output in response.

Create a test file for App in src/__tests__/App.spec.js. Add the code from listing 7 to the describe block in src/__tests__/App.spec.jsfile.

Listing 7 Testing that component responds to Vue custom event

  
 import App from '../App.vue'
 import { shallowMount } from '@vue/test-utils'
 import Modal from '../components/Modal.vue'
  
 describe('App.vue', () => {
   test('hides Modal when Modal emits close-modal', () => {
     const wrapper = shallowMount(App)                ❶ 
     wrapper.find(Modal).vm.$emit('close-modal')      ❷ 
     expect(wrapper.find(Modal).exists()).toBeFalsy() ❸ 
   })
 })
  

❶   Shallow mount the App component

❷   Find wrapper containing Vue instance and emit a close-modal event

❸   Assert that Modal isn’t rendered anymore using the toBeFalsy matcher

You can pass the test by updating the App component to listen to the close-modal event on the Modal component. Open src/App.vue. and replace the Modal start tag with the following code:

 
 <modal
       v-if="displayModal"
       @close-modal="closeModal"
     >
 

And just like that, the tests will pass: npm run test:unit.

You’ve seen how to test write unit tests for components that use DOM events and Vue custom events. You’ll write tests like these often; triggering an event is a common input for components. The principle is simple: you need to trigger or emit an event in the test, and then assert that the tested component responds correctly.

Another common element that uses events is an input form. Input forms often use the value of elements as part of the event listener to do something with, like validating a password. Let’s learn how to write tests for input forms that use native DOM events.

That’s all for part one. Stay tuned for part 2, which explores testing input forms and the limitations of jsdom.

If you want to learn more about the book, check it out on our liveBook reader here and see this slide deck.