Snapshot testing with jest ( example in vue.js )

What is snapshot testing

A simple explanation for snapshot testing is to take a snapshot of the code and compare it to a previously saved snapshot, and if the new snapshot does not match the previous snapshot, the test will fail. Snapshot tests are relatively useful for testing a component, because if a snapshot test is added, it prevents us from modifying the component by mistake.

Process and example of snapshot test

In Jest an automated testing framework, we can use the following code to take a snapshot of a component:

import { shallowMount } from "@vue/test-utils"
import HelloWorld from "@/components/HelloWorld.vue"

it("match snapshot", () => {
  const wrapper = shallowMount(HelloWorld)
  expect(wrapper.element).toMatchSnapshot()
})

The process of the above snapshot test is as follows:

  1. Run a snapshot test.
  2. Generate output.
  3. Whether a snapshot existed before.
  4. Does not exist, create a snapshot, the test passes
  5. If it exists, continue to compare it with the previous snapshot to see if it is the same.
  6. Same, the test passes.
  7. Not the same, the test fails.

An example of a snapshot test is as follows:

exports[`HelloWorld.vue match snapshot 1`] = `
<div>
  <span
    class="item"
  >
    item
  </span>
  Hello, Vue and Jest...
</div>
`

Static component snapshots

It refers to a component that always renders the same output, it does not accept any prop, nor any state, nor any logic inside the component, and will always render the same HTMLelement. Writing unit tests for static components is completely unnecessary, but for a component it is necessary to write a snapshot test for it.

Suppose we have the following static components called spinner:

<template>
  <transition>
    <svg class="spinner" width="22px" height="22px" viewBox="0 0 22 22">
      <circle class="path" fill="none" stroke-width="4" stroke-linecap="round" cx="22" cy="22" r="20">
    </svg>
  </transition>
</template>

We write the following static snapshot test cases:

import { shallowMount } from "@vue/test-utils"
import Spinner from "@/components/spinner.vue"

describe("spinner.vue", () => {
  it("match snapshot", () => {
    const wrapper = shallowMount(Spinner)
    expect(wrapper.element).toMatchSnapshot()
  })
})

Dynamic component snapshots

These are components that contain logic and state, such as props values ​​that are passed when a button is clicked or changes to component data. When writing test cases for dynamic components, you should try to capture the most important branches of logic. Because for a large component, its props own data or other data will affect the rendering result of the component, and it is impossible for us to write a snapshot test for each branch logic.

Suppose we have the following component code:

<template>
  <div>
    <div v-if="age < 10">child person</div>
    <div v-else-if="age >= 10 && age < 30">youth person</div>
    <div>{{ msg }}</div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "default msg"
    }
  },
  data() {
    return {
      age: 11
    }
  }
}
</script>

We write the following two snapshot test cases based on the above code:

import { shallowMount } from "@vue/test-utils"
import PersonDemo from "@/components/PersonDemo.vue"

describe("PersonDemo.vue", () => {
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(PersonDemo)
  })
  it("match msg snapshot", () => {
    expect(wrapper.element).toMatchSnapshot()
  })
  it("match age snapshot", async () => {
    wrapper.setData({
      age: 6
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.element).toMatchSnapshot()
  })
})

After running npm run test:unit, we will get a snapshot like this:

exports[`PersonDemo.vue match age snapshot 1`] = `
<div>
  <div>
     child person 
  </div>

  <div>
    default msg
  </div>
</div>
`

exports[`PersonDemo.vue match msg snapshot 1`] = `
<div>
  <div>
    youth person
  </div>

  <div>
    default msg
  </div>
</div>
`

Update snapshot

We have introduced the methods of writing snapshot tests for static components and dynamic components above, and we also understand the meaning of snapshot testing for a component: when a snapshot test case fails, it prompts us that the component has been modified compared to the last time. If it is unplanned, the test will catch the exception and output it to inform us. If it is planned, then we need to update the snapshot.

There are two main situations for updating snapshots:

Update all: We can use the npm run test:unit -- -u command to batch update our snapshots, but this method is very dangerous, because it is very likely that one of the components is an unplanned update.

Interactive update: We can use the npm run test:unit -- --watch command for Jest interactive update.