Quick Content Testing using Snapshots in Vue.js

Eduardo San Martín

Eduardo San Martín

May 28, 2019
4 min read
Share on Twitter or LinkedIn

Have you ever heard about Snapshots being evil? About how fragile they are and how you should avoid them? It's true! You should be extremely careful about them because they do exact comparison of content as text, that is if you are snapshoting a component, you are actually snapshoting its HTML content, so anything changing the HTML will break the snapshot and if this is repeated too often, you may end up accidentally accepting snapshots updates and missing regressions in your application 😱.

But you don't have to snapshot the whole HTML! You can even provide a hint to recognize the snapshot and this can be used to generate tests fixtures on the flight in a very convening way, specially for very large content sets

Imagine you have a very big table and you want to test that given some props, the table renders the right content:

<table>
  <thead>
    <tr>
      <th v-for="column in columns">{{ column.name }}</th>
    </tr>
  </thead>
  <tbody>
    <tr v-for="item in items">
      <td v-for="column in columns">
        <span class="label">{{ colum.name }}: </span>
        <span class="value">{{ item[colum.key] }}</span>
      </td>
    </tr>
  </tbody>
</table>

Here columns is an array of all the columns in the table and items in an array of all the rows displayed. You could say both are props. If you want to test the content of the table given those props, you will have to test each row:

test('contains the right information', () => {
  // columns and items are defined above
  const wrapper = shallowMount(MyTable { props: { columns, items }})
  // first cell in the header
  expect(wrapper.find('thead th:nth-of-type(1)').text()).toBe('Product')
  // first row in the tbody
  expect(wrapper.find('tbody tr:nth-of-type(1) .value').text()).toBe('Dinner plates set of 8')
  // repeat for EVERY row 🤯
})

There are multiple approaches to select the table cell like using a data-test attribute but that's not the issue here. Can we go faster when writing this kind of test? What if we wrote the component, check manually and then add a test that snapshots the current state?

test('contains the right information', () => {
  // columns and items are defined above
  const wrapper = shallowMount(MyTable { props: { columns, items }})

  const cells = wrapper.findAll("td");
  for (let i = 0; i < cells.length; ++i) {
    const cell = cells.at(i);
    // use label as the hint for the snapshot
    const label = cell.find(".label");
    if (!label.exists()) continue;    // filter out cells that do not have a label
    expect(cell.find(".value").text()).toMatchSnapshot(label.text());
  }
})

Writing this test will generate a snapshot the first time it is run:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MyTable contains the right information: Product Name 1`] = `"Dinner plates set of 8"`;

exports[`MyTable contains the right information: Sells 1`] = `"23"`;

exports[`MyTable contains the right information: Stock 1`] = `"3"`;

// more and more cells

The advantages of this solution is that adding a new column will create a single new snapshots without invalidating the others while removing existing columns will make some snapshot obsoletes and changing any of the cells .value content will make the snapshot test fail.

If you don't like the idea of creating dozens of snapshots like this, you can create some custom text value and create one single snapshot:

test('contains the right information', () => {
  // columns and items are defined above
  const wrapper = shallowMount(MyTable { props: { columns, items }})

  const cells = wrapper.findAll("td");
  let content = ''
  for (let i = 0; i < cells.length; ++i) {
    const cell = cells.at(i);
    // use label as the hint for the snapshot
    const label = cell.find(".label");
    if (!label.exists()) continue; // filter out cells that do not have a label
    content += `${label.text()}: ${cell.find(".value").text()} \n`
  }

  expect(content).toMatchSnapshot()
})

You will end up with one single snapshot:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MyTable contains the right information 1`] = `
Product Name: Dinner plates set of 8
Sells: 23
Stock: 3
etc.
`;

So remember: Snapshots can also be used to generate tests fixtures with text!

Happy Testing!

Don't miss out anything about Vue, your favourite framework.

Subscribe to receive all the articles we publish in a concise format, perfect for busy devs.

Related Articles

Hybrid Rendering: the secret way to smoothly test Vue.js components

Find out how to combine Deep and Shallow Rendering in order to achieve a flexible solution to test your Vue.js combining the best of both worlds.

Alex Jover Morales

Alex Jover Morales

Mar 7, 2022

Deep vs Shallow Rendering in Vue.js Tests

A short article on how to use deep and shallow rendering in Vue.js and what I suggest to use most of the cases using vue test utils.

Alex Jover Morales

Alex Jover Morales

Mar 3, 2020

Testing logic inside a Vue.js watcher

Tutorial on how to test the logic of a vue.js component watcher instead of testing the framework

Javier Martínez

Javier Martínez

Apr 14, 2019

Sponsors

VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.

Silver
Learning Partner