Quick Content Testing using Snapshots in Vue.js
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!
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.
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.
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
Apr 14, 2019
Sponsors
VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.