// Tutorial //

Introduction to Jest Snapshot Testing in Vue.js

Published on March 30, 2020
Default avatar
By Parthiv Mohan
Developer and author at DigitalOcean.
Introduction to Jest Snapshot Testing in Vue.js

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Introduction

Snapshot testing is a type of testing in Jest which monitors regression in your code and also serves as an integration test. The first means that if you add more code to your project and something small breaks, snapshot testing can catch it. The second means that snapshot testing is a way of making sure an entire component runs the way you intend it to.

The way snapshot testing works is that the very first time you run jest, snapshots are generated of the DOM. On subsequent runs of your test suite, the DOM that’s constructed gets compared to these snapshots. Since you may have changed your code, your snapshots still matching the ones generated the first time tells you things are still working.

Some questions naturally come up: what if I make a significant change to your program which results in different DOM contents? Jest allows you to generate new snapshots and such a scenario would warrant that. What if there is non-deterministic content on my page? There are multiple ways to handle this, and we’ll see this shortly!

App Setup

We’ll now set up our application. Head over to the setup section of our tutorial on testing Vue using Jest for setting up a simple app for testing. Here’s what your App.vue file could look like:

App.vue
<template>
  <div id="app">
    <div>
      <h3>Let us test your arithmetic.</h3>
      <p>What is the sum of the two numbers?</p>
      <div class="inline">
        <p>{{ x1 }} + {{ x2 }} =</p> <input v-model="guess"> <button v-on:click="check">Check Answer</button>
      </div>
      <button v-on:click="refresh">Refresh</button>
      <p>{{message}}</p>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      x1: Math.ceil(Math.random() * 100), 
      x2: Math.ceil(Math.random() * 100),
      guess: "",
      message: ""
    }
  },
  methods: {
    check() {
      if (this.x1 + this.x2 === parseInt(this.guess)) {
        this.message = "SUCCESS!"
      } else {
        this.message = "TRY AGAIN"
      }
    },
    refresh() {
      this.x1 = Math.ceil(Math.random() * 100);
      this.x2 = Math.ceil(Math.random() * 100);
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.inline * {
  display: inline-block;
}
img {
  height: 350px;
}
</style>

And here’s what our started app.spec.js looks like:

import { mount } from "@vue/test-utils";
import App from "./../src/App.vue";

describe("App", () => {
  // Inspect the raw component options
  it("has data", () => {
    expect(typeof App.data).toBe("function");
  });
});

describe("Mounted App", () => {
  const wrapper = mount(App);

  test("is a Vue instance", () => {
    expect(wrapper.isVueInstance()).toBeTruthy();
  });

  it("renders the correct markup", () => {
    expect(wrapper.html()).toContain(
      "<p>What is the sum of the two numbers?</p>"
    );
  });

  // it's also easy to check for the existence of elements
  it("has a buttons", () => {
    expect(wrapper.contains("button")).toBe(true);
  });

  it("renders correctly with different data", async () => {
    wrapper.setData({ x1: 5, x2: 10 });
    await wrapper.vm.$nextTick();
    expect(wrapper.text()).toContain("10");
  });

  it("button click without correct sum", () => {
    expect(wrapper.vm.message).toBe("");
    const button = wrapper.find("button");
    button.trigger("click");
    expect(wrapper.vm.message).toBe("TRY AGAIN");
  });

  it("button click with correct sum", () => {
    wrapper.setData({ guess: "15" });
    const button = wrapper.find("button");
    button.trigger("click");
    expect(wrapper.vm.message).toBe("SUCCESS!");
  });
});

Keep in mind that it is just an alias for test in Jest. Run npm run test and all tests should pass.

Let’s Start Snapshot Testing!

Run npm install --save-dev jest-serializer-vue then make the below addition to package.json

package.json
{
  ...
  "jest": {
    "snapshotSerializers": ["jest-serializer-vue"]
  },
  ...
}

Add some code to the second describe block.

it('renders correctly', () => {
  const wrapper = mount(App)
  expect(wrapper.element).toMatchSnapshot()
})

Run your tests and notice how the first time you run your tests you should see “1 snapshot written”. Notice that a directory called __snapshots__ has been created next to app.spec.js.

Feel free to take a look at the snapshot file, which has a file ending with the .snap extension; you’ll notice that the whole template section of the component has been reproduced, except for attributes that have the prefix v-.

Run your tests again.

Error!

Why?!

If you examine the snapshot test’s output in your Terminal, it’s clear why: we have randomly generated numbers on our page. You should also be able to see what the numbers in the snapshot are. Go ahead and substitute those into your test by passing data when you mount your component; the function you pass will be merged into the component’s own data.

It should look something like this once you have it passing again:

it('renders correctly', () => {
  const wrapper = mount(App, {
    data() {
      return {
        x1: 37,
        x2: 99
      }
    }
  })    
  expect(wrapper.element).toMatchSnapshot()
})

Another approach is to write a mock function for the nondeterministic function in our code. In our case, that is Math.random().

You’d wind up with something like the following:

it('renders correctly with mock', () => {
  Math.random = jest.fn(() => .37);
  const wrapper = mount(App)    
  expect(wrapper.element).toMatchSnapshot()
})

Let’s say we wanted to move our header to be above the photo on the page. This is an easy modification to your Vue component so go ahead and do that. Try running your test suite again.

Error!

It failed because our snapshot has the page arranged differently. We must update that part of our snapshot and we can do that by running npm test -- -u.

Now our tests pass again.

Success!

If you wanted to update snapshots interactively, you can run npm test -- -i.

Conclusion

Snapshots can be very useful in keeping abreast of any accidental changes to your application’s interface. Snapshots should be checked into Git like any other code. If tests fail, check what has happened before reflexively updating your snapshots.

Snapshot testing should be very useful to you in testing your Vue applications, especially as they get more complex. Good luck out there!


Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Still looking for an answer?

Was this helpful?
Leave a comment

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!