shallowMount vs mount in vue test utils with an example

shallow mount vs mount

In this post, we will see the differences between shallowMount and mount functions in vue-test-utils library and what they both offer us in terms of unit testing in Vue applications.

If you want to know how to set up unit tests for the Vue project, check my article on how to do it.

Let’s have an App.vue and HelloWorld.vue files and HelloWorld component is a child component of App.vue.

The App component passes msg as a prop to the HelloWorld component and the hello world component will display the passed message.

Hello world component is a child of App.vue component

The above code diagram is our example for this blog post to understand mount and shallowMount functions.

What is a mount() function?

The mount function takes in the Vue component as a first argument and has options as a second argument. The mount function will return Vue instance wrapper of the component passed in.

So, If I mount the App component, I can have access to the child component’s DOM elements as well.

The purpose of the mount function is (per my understanding)

When changing a property or something in the parent component, If there’s a change to the DOM element in the child component, then we can assert the child element properties to see if it matches the expectation.

Mount() function in Action

Here is the App.vue component.

<template>
  <div id="app">
    <HelloWorld :msg="msg" />
  </div>
</template>
...
export default {
  name: "App",
  components: {
    HelloWorld
  },
  data() {
    return {
      msg: "Welcome to Your Vue.js App"
    };
  }
};

Now, let’s write unit tests for App.vue file.

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

describe("App.vue Tests", () => {
  it("message passed from App -> Hello World should be rendered", () => {
    const msg = "Hello World";
    const wrapper = mount(App, {
      data() {
        return {
          msg: msg
        };
      }
    });
    expect(wrapper.find("div.hello > h1").text()).toBe(msg);
  });
});

We set up our mount function to have our custom message(msg) in the data() function. The msg variable is passed on to the HelloWorld component from the App component. And our text in the H1 tag should now reflect what we have in the msg variable in the test.

So, if you want to write tests that include testing a child component’s also, then you can use the mount function on the parent element to render the component with its children and assert what you want.

What is a shallowMount() function?

Unlike mount function, shallowMount function will just load the component itself ignoring the child component(s).

If you want to write unit tests for a component, then shallowMount function is the right choice.

If we replace the mount function with shallowMount in the above mounted tests for App component, the test will fail as the shallowMount cannot load the HelloWorld component and unable to locate the div.hello > h1 in the component.

Let me run the test by replacing mount >> shallowMount and see what happens.

it("message passed from App -> Hello World will not be rendered", () => {
    const msg = "Hello World";
    const wrapper = shallowMount(App, {
      data() {
        return {
          msg: msg
        };
      }
    });
    expect(wrapper.find("div.hello > h1").text()).toBe(msg);
  });

Here’s the output after running the test

  ● App.vue Tests › message passed from App -> Hello World will not be rendered

    [vue-test-utils]: find did not return div.hello > h1, cannot call text() on empty Wrapper

      12 |       }
      13 |     });
    > 14 |     expect(wrapper.find("div.hello > h1").text()).toBe(msg);
         |                                           ^
      15 |   });
      16 | });
      17 | 

      at throwError (node_modules/@vue/test-utils/dist/vue-test-utils.js:1709:9)
      at Object.<anonymous> (tests/unit/app.spec.js:14:43)

The wrapper is empty after finding the div.hello > h1. So, the test failed.

Writing unit test with shallowMount function

How to pass the above failing unit test?

Instead of testing the App.vue component, test the HelloWorld component alone by using the shallowMount function. As we are interested in checking what the HelloWorld component renders in H1 tag when a message is passed.

Before we write a test for HelloWorld component, here’s what the Hello World component looks like.

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
  ...
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String
  }
};
</script>

Let’s write a test for HelloWorld component.

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

describe("HelloWorld.vue", () => {
  it("H1 tag should render what is passed into the props", () => {
    const msg = "Hello World again";
    const wrapper = shallowMount(HelloWorld, {
      propsData: {
        msg: msg
      }
    });
    expect(wrapper.find("h1").text()).toBe(msg);
  });
});

We will use the propsData option to pass our data to the msg prop and verify if we got what we have passed in using the expect function.

If we run, the test should pass now.

Conclusion

If you are writing unit tests and want to test the functionality of a component individually, then setup the shallowMount function and test it.

If you want to test a nested component, then use the mount function.

I’d recommend using shallowMount function, as it’s for unit testing a component. If you have a nested component, then write tests for the component.

References