SoFunction
Updated on 2025-03-08

Specific use of snapshot tests in Vue projects

Introduction to snapshot

snapshot test, also known as snapshot test, can intuitively reflect whether the component UI has undergone unforeseen changes. snapshot, as literally, intuitively describes what the component looks like. By comparing the snapshots before and after, you can quickly find out the changes in the UI.

A snapshot file is generated when the snapshot test is run for the first time. After that, each time the test is executed, a snapshot will be generated, and the original generated snapshot file will be compared. If there is no change, the test will be passed. Otherwise, the test fails and the results will be output at the same time, and the mismatch will be compared.

The snapshot file in jest ends with the snap extension name, and the format is as follows (ps: Before I knew it, I thought it was a snapshot file, which was a screenshot). A snapshot file can contain multiple snapshots, and the format of the snapshot is actually an HTML string. For UI components, its HTML will reflect its internal state. Each test only needs to compare whether the string meets the initial snapshot.

exports[`button 1`] = `"<div><span class=\\"count\\">1</span> <button>Increment</button> <button class=\\"desc\\">Descrement</button> <button class=\\"custom\\">not emitted</button></div>"`;

There are two reasons why the snapshot test fails. One reason is that the component has undergone an unexpected change, and the code should be checked at this time. Another reason is that the component is updated and the snapshot file is not updated. At this time, you need to run jest -u to update the snapshot.

› 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with -u to update them.

Snapshot test combined with Vue

The components need to be rendered and mounted when generating snapshots. You can use the official unit testing utility in Vue.Vue Test Utils

Vue Test Utils provides two methods: mount and shallowMount, which are used to create a Wrapper containing Vue components that are mounted and rendered. component is a vue component, options are the configuration when instantiating Vue, includingMounting optionsand other options (non-mount options, overriding them to their component options via extend), resulting in a Wrapper instance that includes a mount component or vnode, and a method that tests that component or vnode.

mount(component:{Component}, options:{Object})

The difference between shallowMount and mount is the subcomponent that is stubbed. Please click on it for details.document

The rich properties and methods on Wrapper are enough to meet the testing needs in this article. The html() method returns the HTML string of the Wrapper DOM node. find() and findAll() can find DOM nodes or Vue components in Wrapper, and can be used to find elements that listen to events. A trigger can trigger an event on a DOM node/component.

Combining the above method, we can complete a snapshot test that simulates event triggers.

Careful readers may find that when we use Vue, the view after data is updated will not be updated immediately, and we need to process the tasks after the update is completed in the nextTick callback. But in Vue Test Utils, for simplifying usage, updates are synchronized, so there is no need to use in tests to wait for DOM updates.

demo demo

Vue Test Utils official documentation provides a VTU and Jest integrationdemo, but this demo is relatively old, and the official recommendation is to create projects with CLI3.

Execute vue create vue-snapshot-demo to create demo project. When creating, you need to select unit tests. The libraries provided include Mocha + Chai and Jest. Select Jest here. After the installation is completed, run npm run serve to run the project.

This article will use a simple oneTodo application projectCome to demonstrate. This Todo application has the function of simply adding, deleting and modifying the status of Todo items; the status of Todo items is completed and unfinished, and cannot be deleted when completed, and can be deleted when not completed; the completed Todo items will be crossed with a line, and the unfinished items will display the delete button when floating on the mouse.

Components are simply divided into Todo and TodoItem. TodoItem displays a delete button when the Todo item is not completed and the mouseover event is triggered, and hides the button when the mouseleave is triggered (this can simulate the event in the snapshot test). There is a checkbox in TodoItem that switches the state of Todo items. When the Todo item is completed, there will be a todo-finished class to achieve the strikethrough effect.

For convenience, only the code and test of the TodoItem component are introduced here.

<template>
 <li
  :class="['todo-item', ?'todo-finished':'']"
  @mouseover="handleItemMouseIn"
  @mouseleave="handleItemMouseLeave"
 >
  <input type="checkbox" v-model="">
  <span class="content">{{}}</span>
  <button class="del-btn" v-show="!&&hover" @click="emitDelete">delete</button>
 </li>
</template>

<script>
export default {
 name: "TodoItem",
 props: {
  item: Object
 },
 data() {
  return {
   hover: false
  };
 },
 methods: {
  handleItemMouseIn() {
    = true;
  },
  handleItemMouseLeave() {
    = false;
  },
  emitDelete() {
   this.$emit("delete");
  }
 }
};
</script>
<style lang="scss">
.todo-item {
 list-style: none;
 padding: 4px 16px;
 height: 22px;
 line-height: 22px;
 .content {
  margin-left: 16px;
 }
 .del-btn {
  margin-left: 16px;
 }
 &.todo-finished {
  text-decoration: line-through;
 }
}
</style>

When performing snapshot testing, you can simulate events in addition to testing whether the data rendering is correct. I will only post the code for snapshot test cases here, and click on me for the complete code.

describe('TodoItem snapshot test', () => {
  it('first render', () => {
    const wrapper = shallowMount(TodoItem, {
      propsData: {
        item: {
          finished: true,
          content: 'test TodoItem'
        }
      }
    })
    expect(()).toMatchSnapshot()
  })

  it('toggle checked', () => {
    const renderer = createRenderer();
    const wrapper = shallowMount(TodoItem, {
      propsData: {
        item: {
          finished: true,
          content: 'test TodoItem'
        }
      }
    })
    const checkbox = ('input');
    ('click');
    (, (err, str) => {
      expect(str).toMatchSnapshot()
    })
  })
  
  it('mouseover', () => {
    const renderer = createRenderer();
    const wrapper = shallowMount(TodoItem, {
      propsData: {
        item: {
          finished: false,
          content: 'test TodoItem'
        }
      }
    })
    ('mouseover');
    (, (err, str) => {
      expect(str).toMatchSnapshot()
    })
  })
})

Here are three tests. The second test simulates checkbox click, switches the Todo item from completed to unfinished, and expects that the class todo-finished will be removed. The third test simulates mouse levitation on the unfinished Todo item, triggers the mouseover event, and expects that the delete button will be displayed.

Here we use toMatchSnapshot() to perform matching snapshots. There are two ways to generate a snapshot file here () and these methods. The difference is that the former is synchronous acquisition, while the latter is asynchronous acquisition.

When testing mock events, it is best to get HTML strings asynchronously. The string obtained by synchronization is not necessarily a view after the UI is updated.

Although the VTU document says that all updates are synchronized, in fact, in the second snapshot test, if you use expect(()).toMatchSnapshot(), the Todo item in the generated snapshot file still has class todo-finished. The expected result should be that there is no class todo-finished, and the result is not the updated view. In the third snapshot test, the snapshot generated by expect(()).toMatchSnapshot() is displayed as expected, and is the updated view of the UI. Therefore, it is not recommended to use () to get HTML strings when DOM is updated.

The following are the results of two comparisons. 1 is a snapshot generated using () and 2 is a generated using using.

exports[`TodoItem snapshot test mouseover 1`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="">delete</button></li>`;

exports[`TodoItem snapshot test mouseover 2`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn">delete</button></li>`;

exports[`TodoItem snapshot test toggle checked 1`] = `<li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display: none;">delete</button></li>`;

exports[`TodoItem snapshot test toggle checked 2`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display:none;">delete</button></li>`;

Used herevue-server-rendererThe createRenderer provided is to generate a Renderer instance, and the instance method renderToString is to obtain the HTML string. This is a typical callback style, and assertion statements can be executed in the callback.

  // ...
  ('mouseover');
  (, (err, str) => {
    expect(str).toMatchSnapshot()
  })

If you do not want to use this library, you can also use the provided one provided in VTUAsynchronous case. Since () is a synchronous acquisition, the acquisition operation and assertion statement need to be executed in the promise returned by ().

  // ...
  ('mouseover');
  ().then(()=>{
    expect(()).toMatchSnapshot()
  })

Observe the test results

Execute npm run test:unit or yarn test:unit to run the test.

For the first execution, the terminal output will have the Snapshots: 3 written, 3 total, which means that three new snapshot tests are added and the initial snapshot file is generated.

 › 3 snapshots written.
Snapshot Summary
 › 3 snapshots written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:    7 passed, 7 total
Snapshots:  3 written, 3 total
Time:    2.012s
Ran all test suites.
Done in 3.13s.

The snapshot file is as follows:

// Jest Snapshot v1, /fbAQLP

exports[`TodoItem snapshot test first render 1`] = `<li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display: none;">delete</button></li>`;

exports[`TodoItem snapshot test mouseover 1`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn">delete</button></li>`;

exports[`TodoItem snapshot test toggle checked 1`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display:none;">delete</button></li>`;

After the second test is executed, there are Snapshots: 3 passed, 3 total in the output, indicating that three snapshot tests have been successfully passed, and a total of three snapshot tests are present.

Test Suites: 1 passed, 1 total
Tests:    7 passed, 7 total
Snapshots:  3 passed, 3 total
Time:    2s
Ran all test suites.
Done in 3.11s.

Modify the content passed in the first snapshot. When rerun the test, the terminal will output mismatches. The format of the output data is similar to Git. It will indicate which line is added and which line is deleted, and prompt that the line where the code is located does not match.

  - Snapshot
  + Received

  - <li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display: none;">delete</button></li>
  + <li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem content change</span> <button class="del-btn" style="display: none;">delete</button></li>

   88 |       }
   89 |     })
  > 90 |     expect(()).toMatchSnapshot()
     |                ^
   91 |   })
   92 |
   93 |   it('toggle checked', () => {

   at  (tests/unit/:90:32)

You will also be reminded to check whether the code is wrong or rerun the test and provide parameters -u to update the snapshot file.

Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with `-u` to update them.

Execute npm run test:unit -- -u or yarn test:unit -u to update the snapshot. The output is as follows. You can find that the output of a snapshot test has been updated. The file you compare to next snapshot test is this updated file.

Test Suites: 1 passed, 1 total
Tests:    7 passed, 7 total
Snapshots:  1 updated, 2 passed, 3 total
Time:    2.104s, estimated 3s
Ran all test suites.
Done in 2.93s.

other

In addition to using toMatchSnapshot(), you can also use toMatchInlineSnapshot(). The difference between the two is that toMatchSnapshot() looks for snapshots from the snapshot file, while toMatchInlineSnapshot() matches the passed parameters as the snapshot file.

Configure Jest

Jest configuration can be saved in a file, it can be saved in it, represented by the key name jest, and also allows in-line configuration.

A few commonly used configurations are introduced.

rootDir

Find the directory for Jest configuration, the default ispwd

testMatch

The rule for finding matching test files is [ "**/__tests__/**/*.js?(x)", "**/?(*.)+(spec|test).js?(x)" ]. By default, the js/jsx file in the __test__ folder and the js/jsx file ending in .test/.spec include the and.

snapshotSerializers

The HTML text in the generated snapshot file has no line breaks. Can line breaks beautify it? The answer is yes.

You can add snapshotSerializers to the configuration, accept an array, and process the matching snapshot files. This is what the jest-serializer-vue library does.

If you want to implement this your own serialization task, the methods you need to implement are test and print. test is used to filter processed snapshots, and print returns processed results.

postscript

Before I knew about the test, I always thought it was boring. After understanding snapshot testing, I found that the test is actually quite interesting and practical, and at the same time I sincerely lament the cleverness of snapshot testing. If this simple case can help you understand the role and usage of snapshot testing, it will be my biggest gain.

If there are any problems or errors, please point out and communicate.

Reference link

vue-test-utils-jest-example
Jest - Snapshot Testing
Vue Test Utils
Vue SSR Guide

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.