Chapter 9

Snapshot Testing

So far, you've seen how you can test the structure, styles, methods, computed properties, events, watchers, and more of Vue.js components. And you've learned to do this by using a variety of techniques and methods.

But what if I tell you that you can test most of it by simply using snapshot testing?

You've already seen snapshot testing being used in Chapter 1, Write the first Vue.js Component Unit Test in Jest and Chapter 2, Test Deeply Rendered Vue.js Components but those chapters focused more on explaining shallow and deep rendering, so I haven't yet explained it in detail.

Snapshot testing is the technique of assertion by comparing two different outputs.

Think of it as something similar to the screenshot technique used in end-to-end tests to check regressions: the first test run takes a screenshot of a part of the screen (for instance, a button), and, from that moment on, all the following runs of the same test will compare a new screenshot with the original one. If they're the same, the test passes; otherwise, there is a regression.

Snapshot testing works in the same way, but instead of comparing images, it compares serializable output, such as JSON and HTML, or just strings.

Since Vue.js renders HTML, you can use snapshot testing to assert the rendered HTML, given different states of a component.

Rethinking in Snapshots

For this example, let's consider the following ContactBox.vue component:

<template>

  <div :class="{ selected: selected }" @click="handleClick">

    <p>{{ fullName }}</p>

  </div>

</template>

<script>

  export default {

    props: ["id", "name", "surname", "selected"],

    computed: {

      fullName() {

        return `${this.name} ${this.surname}`;

      }

    },

    methods: {

      handleClick() {

        this.$emit("contact-click", this.id);

      }

    }

  };

</script>

In this case, we can test several aspects of this component:

  • fullName is the combination of name + surname.
  • It has a selected class when the component is selected.
  • It emits a contact-click event.

One way to create tests that validate these specifications would be to check everything separately – the classes attached to the DOM elements, the HTML structure, the computed properties, and the state.

As you've seen in other chapters, you could perform these tests as follows:

import { mount } from "vue-test-utils";

import ContactBox from "../src/components/ContactBox";

const createContactBox = (id, name, surname, selected) =>

  mount(ContactBox, {

    propsData: { id, name, surname, selected }

  });

describe("ContactBox.test.js", () => {

  it("fullName should be the combination of name + surname", () => {

    const cmp = createContactBox(0, "John", "Doe", false);

    expect(cmp.vm.fullName).toBe("John Doe");

  });

  it("should have a selected class when the selected prop is true", () => {

    const cmp = createContactBox(0, "John", "Doe", true);

    expect(cmp.classes()).toContain("selected");

  });

  it("should emit a contact-click event with its id when the component is clicked", () => {

    const cmp = createContactBox(0, "John", "Doe", false);

    cmp.trigger("click");

    const payload = cmp.emitted("contact-click")[0][0];

    expect(payload).toBe(0);

  });

});

But now, let's think about how snapshot testing can help us here.

If you think about it, the component renders according to its state. Let's refer to this as the rendering state.

With snapshot testing, instead of worrying about checking for specific things, such as attributes, classes, methods, and computed properties, we can instead check the rendering state, as this is the projected result of the component state.

For this, you can use snapshot testing for the previous test as follows:

it("fullName should be the combination of name + surname", () => {

  const cmp = createContactBox(0, "John", "Doe", false);

  expect(cmp.element).toMatchSnapshot();

});

As you can see, instead of now checking things separately, I'm just asserting the snapshot of cmp.element, this being the rendered HTML of the component.

If you run the test suite now, a ContactBox.test.js.snap file should have been created and you'll see a message in the console output as well:

Figure 9.1

Figure 9.1

Let's analyze the snapshot generated:

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

exports[

  `ContactBox.test.js fullName should be the combination of name + surname 1`

] = `

<div

  class=""

>

  <p>

    John Doe

  </p>

</div>

`;

The purpose of this test is to check that the computed property, fullName, combines both the name and surname, separated by a space. Looking at the snapshot, you can see that this is happening and that John Doe is there, so you may consider this test valid.

In the same way, you can write the second test using snapshot testing:

it("should have a selected class when the selected prop is true", () => {

  const cmp = createContactBox(0, "John", "Doe", true);

  expect(cmp.element).toMatchSnapshot();

});

Notice that the only aspect that changes between this test and the previous one is the setting of the selected property to true.

That's the power of snapshot testing: you play with different states of the components, while you just need to assert the rendering state.

The purpose of this test is to validate that it has a selected class when the property is true. Now, let's run the test suite again, and if you check ContactBox.test.js.snap again, you'll see that another snapshot has been added:

exports[

  `ContactBox.test.js should have a selected class when the selected prop is true 1`

] = `

<div

  class="selected"

>

  <p>

    John Doe

  </p>

</div>

`;

And the selected class is there, as expected, so we can consider this one to also be valid.

When Snapshot Testing Doesn't Help

Have you noticed that I didn't mention anything about the third test? To recall this test, let's check it again:

it("should emit a contact-click with its id when the component is clicked", () => {

  const cmp = createContactBox(0, "John", "Doe", false);

  cmp.trigger("click");

  const payload = cmp.emitted("contact-click")[0][0];

  expect(payload).toBe(0);

});

In this case, when the component is clicked, it doesn't perform any action that changes the component state, which means that the rendering state won't change. We're just testing behavior here that has no effect on the rendering of the component.

For that reason, we can say that snapshot testing is useful for checking changes in the rendering state. If the rendering state doesn't change, there is no way that snapshot testing can help us.

When a Test Fails

The snapshots generated are the source of truth when it comes to deciding whether a test is valid. That's the way regressions are checked, and, ultimately, that depends on your criteria.

For example, go to the ContactBox.vue component and change the fullName computed property to be separated by a comma:

fullName() {

  return `${this.name}, ${this.surname}`;

}

If you run the tests again, some of them will fail since the rendering result is different from before. You'll get an error along the lines of the following:

Received value does not match stored snapshot 1.

  - Snapshot

  + Received

    <div

      class=""

    >

      <p>

  - John Doe

  + John, Doe

      </p>

    </div>

From that point on, as it's usually in relation to testing, you must decide whether that's an intentional change or whether it's a regression. You can press 'u' in order to update the snapshots:

Figure 9.2

Figure 9.2

It would be convenient when applying TDD to use the watch mode, npm run test -- --watch. This would be very convenient since Jest gives you a number of options for updating snapshots:

  • Press 'u' to update all snapshots.
  • Press 'i' to update snapshots interactively, one by one.

Conclusion

Snapshot testing saves you a lot of time. This example was basic, but imagine testing a more complex component with many different rendering states...

Sure, you can assert in relation to specific things, but that's much more cumbersome than asserting how the component is rendered depending on the state, since, most of the time, if you change the code, you have to change the assertions in relation to the tests, while, with snapshot testing, you don't need to.

Additionally, you can find regressions that you didn't take into account, perhaps something you didn't consider in your tests, or something that has changed the rendering of the component, but the snapshots will alert you to this.

I would now like to mention a number of caveats that you should remember:

  • Snapshot testing doesn't replace specific assertions. While it can do so most of the time, both ways of testing are totally combinable.
  • Don't update snapshots too easily. If you see that a test fails because it doesn't match a snapshot, take an in-depth look at it before updating it too quickly. I've been there as well.

If you want to try it yourself, you can find the full example used in this chapter on GitHub (https://github.com/alexjoverm/vue-testing-series/tree/chapter-9).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.73.125