DEV Community

Cover image for Extending Vue Test Utils
Daniel P πŸ‡¨πŸ‡¦
Daniel P πŸ‡¨πŸ‡¦

Posted on

Extending Vue Test Utils

The Vue Test Utils is the official unit testing library for you Vue.js components. It works with test runners, such as mocha or jest, to allow making assertions and executing interactions on the Vue components.

The library offers many helpful functions, but for the most part they are fairly low-level.

Some of my test involve dealing with lists of items that may have this kind of layout. This might be used for the checkbox css styling hack, where a checkbox is hidden using css, but the label is used to toggle the value.

<div>
  <label class="my-input">
    Audi
    <input v-model="vehicle" value="0" type="checkbox"/>
  </label>
  <label class="my-input">
    Saab
    <input v-model="vehicle" value="1" type="checkbox"/>
  </label>
  <label class="my-input">
    Volvo
    <input v-model="vehicle" value="2" type="checkbox"/>
  </label>
  <!-- etc... ->
</div>
Enter fullscreen mode Exit fullscreen mode

In your test, you may want to toggle the values by triggering a click event on a label element.

Let's say, that for this test you want to simulate the click on the Saab label element.

The Saab text is a child of the label tag, so you cannot use a simple selector for the label tags, since the items look the same (at the top level).

There are three common ways of dealing with it

  • custom test-only data
  • use n-th child
  • use child to find text

adding custom attributes for test only

You could add a parameter like data-test to make testing easier:

<div>
  <label class="my-input" data-test="label-audi">
    Audi
    <input v-model="vehicle" value="0" type="checkbox"/>
  </label>
  <label class="my-input" data-test="label-saab">
    Saab
    <input v-model="vehicle" value="1" type="checkbox"/>
  </label>
  <label class="my-input" data-test="label-volvo">
    Volvo
    <input v-model="vehicle" value="2" type="checkbox"/>
  </label>
  <!-- etc... ->
</div>
Enter fullscreen mode Exit fullscreen mode
const wrapper = shallowMount(Foo);
const labelSaab = wrapper.find("label[data-test='label-saab']");
Enter fullscreen mode Exit fullscreen mode

While there are many developers advocating for this, I prefer not exposing these values that are for tests only. This is a personal preference, and doesn't mean that it is wrong.

N-th child

const wrapper = shallowMount(Foo);
const labels = wrapper.findAll('label');
const labelSaab = labels.at(1);
Enter fullscreen mode Exit fullscreen mode

This relies on making some assumptions; namely that the index of the expected element is correct.

You can reduce the risk of selecting the wrong item by validating the text content, but the downside is that you still need to keep track of the name and the index separately, which might make the test less readable

Use child text to find the correct item

This is the the most complex of all, since it there are more steps to find the correct label.

The test runner needs to find all labels and loop through them. Then for each label, it will go through the children, and check for a string match.

const wrapper = shallowMount(Foo);
const labels = wrapper.findAll('label');
const labelSaab = labels.filter(i => i.text().match('Saab')).at(0);
Enter fullscreen mode Exit fullscreen mode

This is not much more complicated than the other versions, but it is more verbose, and less intuitive/readable (especially compared to the first option)

The way I've been dealing with these cases is by wrapping the wrapperArray with custom functionality to suit the layout of my components, which helps make the code less verbose and more readable.

Wrapper functions

this is an example of some of the functions I may use either with assertions or as selectors

function withWrapper(wrapper) {
  return {
    find: (selector) => ({
      childSelectorHasText: (childSelector, str) => wrapper.findAll(selector).filter(i => i.find(childSelector).text().match(str)),
      withText: (str) => wrapper.findAll(selector).filter(i => i.text().match(str)).at(0),
    }),
    areVisible: () => wrapper.findAll(selector).wrappers.filter(w => w.isVisible()).length,
    areHidden: () => wrapper.findAll(selector).wrappers.filter(w => !w.isVisible()).length,
    areAllVisible: () => wrapper.findAll(selector).wrappers.every(w => w.isVisible()),
    areAllHidden: () => wrapper.findAll(selector).wrappers.every(w => !w.isVisible()),
  }
}


Enter fullscreen mode Exit fullscreen mode

by making that function available in your test spec you can then do things like...

const wrapper = shallowMount(Foo);
const labelSaab = withWrapper(wrapper).find('label').withText('Saab');

// and ...
const numLabelsVisible = withWrapper(wrapper).areVisible('label');
const allLabelsVisible = withWrapper(wrapper).areAllVisible('label');
Enter fullscreen mode Exit fullscreen mode

Which, I believe, makes the tests a lot more readable. These functions can abstract the complex repetitive tasks and add functionality that you might find is missing from the library.

Photo Credit: by ShareGrid on Unsplash

Latest comments (2)

Collapse
 
alexanderop profile image
Alexander Opalic

The idea with the high order function for the wrapper is really nice

Collapse
 
angieg0nzalez profile image
Angelica Gonzalez

Thanks for this!