Mocking a getter in Vuex + Jest

Categories :

Table of Contents

Let’s say you want to unit test a component which behavior depends on a Vuex getter.

Here's an example

Let’s use the imaginary example of a Pizza component with a isVegan computed property that depends on the recipe Vuex getter: a margherita pizza is not vegan, but a zucchini pizza is.

You don’t want to use the “real” getter, because that would not be a unit test anymore, so you mock it, and all goes well at first:

import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import Pizza from './Pizza.vue';

const localVue = createLocalVue();
localVue.use(Vuex);

const recipeMock = jest.fn();
recipeMock.mockReturnValue('margherita');
const store = new Vuex.Store({
  getters: {
    recipe: recipeMock,
  },
});
const wrapper = shallowMount(Pizza, {
  localVue,
  store,
});

expect(wrapper.vm.isVegan).toBeFalsy(); // pass

You move on to testing what happens when the getter updates, and with horror, you observe that the test fails:

recipeMock.mockReturnValue('zucchini');
expect(wrapper.vm.isVegan).toBeTruthy(); // fail

Trying to understand what is happening, you add logs in between your lines:

console.log(
  wrapper.vm.$store.getters.recipe, // margherita
  wrapper.vm.isVegan, // false
)
recipeMock.mockReturnValue('zucchini');
console.log(
  wrapper.vm.$store.getters.recipe, // margherita
  wrapper.vm.isVegan, // false
)
expect(wrapper.vm.isVegan).toBeTruthy(); // fail

 

It won't update?

Argh, the getter is not updating, even though we clearly ask the mock to return something else!

Why?

Because Vue is reactive: it doesn’t update anything unless it tracked an event that is explicitly changing the state of something. Sometimes, it’s hidden for convenience, like when we update data properties, but there is no such thing happening here and Vue can’t make the link between the mock updating its return value and the getter value updating.

Commit a mutation

The only way to update the getter is to commit a mutation that will impact a state property that is used in computing the getter. That would look something like this:

const localVue = createLocalVue();
localVue.use(Vuex);

const store = new Vuex.Store({
  state: {
    testRecipe: 'margherita',
  },
  mutations: {
    setTestRecipe(state, value) {
      state.testRecipe = value;
    },
  },
  getters: {
    recipe(state) {
      return state.testRecipe;
    },
  },
});

Yes, it’s not perfect because we’re introducing state properties and mutations that don’t actually exist, but given the right prefix or comment to signal that, it’s an acceptable tradeoff.

And it works!

const wrapper = shallowMount(Pizza, {
  localVue,
  store,
});

expect(wrapper.vm.isVegan).toBeFalsy(); // pass
store.commit('setTestRecipe', 'zucchini');
expect(wrapper.vm.isVegan).toBeTruthy(); // pass
 

 

Toucan is a customer-facing analytics platform that empowers companies to drive engagement with data storytelling. Ranked the #1 easiest to use analytics solution by Gartner Peer Insights, their no-code, cloud-based platform cuts custom development costs with a quick and easy implementation, even for non-technical builders. To find out what our users say, read their reviews on Capterra.

This is a heading 2

This is a paragraph

This is a heading 2

This is a paragraph

Table of Contents