banner cross

By using our website, you accept that we use cookies to perform analytics and produce content & ads tailored to your interests.
Read our policy on cookies usage.

Mocking a getter in Vuex + Jest

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

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

Argh, the getter is not updating, eventhough 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.

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 introducting 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