How to test Vue watcher that watches a computed property from VueX?

11,146

Solution 1

From you're trying to achieve

When testing, I want to ensure that externalDependency.doThing() is called with bar (which comes from the vuex state) like so:

(and this is indeed pure unit test approach), you can just force change of this watcher, which basically is a function. There's no need to track if watcher is changing in case of computed or data value change - let Vue handle it. So, to change a watcher in a mounted Vue instance, just call it like

wrapper.vm.$options.watch.bar.call(wrapper.vm)

Where bar is name of your watcher. This way you will be able to test exact functionality that you're aiming to test.

Idea taken from this comment https://github.com/vuejs/vue-test-utils/issues/331#issuecomment-382037200, on a vue-test-utils issue, mentioned by you in a question.

Solution 2

The Vue Test Utils documentation points at a different approach where you use a very simple Vuex store:

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

// use a localVue to prevent vuex state from polluting the global Vue instance
const localVue = createLocalVue();
localVue.use(Vuex);

describe('Foo.vue', () => {
  let state;
  let store;

  beforeEach(() => {
    // create a new store for each test to prevent pollution
    state = { bar: 'bar' };
    store = new Vuex.Store({ state });
  })

  it('should call externalDependency.doThing with bar', () => 
  {
    shallowMount(MyComponent, { store, localVue });
    const spy = jest.spyOn(externalDependency, 'doThing');
    // trigger the watch
    state.bar = 'baz';
    expect(spy).toHaveBeenCalledWith('baz');
  });
})
Share:
11,146

Related videos on Youtube

yxu296
Author by

yxu296

Updated on June 04, 2022

Comments

  • yxu296
    yxu296 almost 2 years

    Suppose I have the following component:

    import { mapState } from 'vuex';
    import externalDependency from '...';
    
    export default {
      name: 'Foo',
      computed: {
        ...mapState(['bar'])
      },
      watch: {
        bar () {
         externalDependency.doThing(this.bar);
        }
      }
    }
    

    When testing, I want to ensure that externalDependency.doThing() is called with bar (which comes from the vuex state) like so:

    it('should call externalDependency.doThing with bar', () => {
      const wrapper = mount(Foo);
      const spy = jest.spyOn(externalDependency, 'doThing');
    
      wrapper.setComputed({bar: 'baz'});
    
      expect(spy).toHaveBeenCalledWith('baz');
    });
    

    Vue test-utils has a setComputed method which allows me to currently test it, but I keep getting warnings that setComputed will be removed soon, and I don't know how else this can be tested:

    https://github.com/vuejs/vue-test-utils/issues/331

    • clay
      clay about 6 years
      If you commit a change to Vuex, the computed property will update via Vue. I do not know if the watch will trigger, but setting the Vuex state rather than changing the computed directly seems like it should work.
    • yxu296
      yxu296 about 6 years
      but that goes against the spirit of a unit test--- it becomes more of an e2e test. You shouldn't have to mock out vueX functionality just when testing a VueX connected component
    • Phil
      Phil over 4 years
      Given Vuex's mapState is an input to your component, that's what you should be mocking. Using setComputed does not test your app correctly
    • ierdna
      ierdna over 4 years
      there's nothing wrong with importing/mocking the VueX store for unit tests. normally you'd mock them in beforeEach(), so your it() blocks are lean. as your component grows, you'll be doing dispatch and watching the state itself possibly, so it'll become very awkward mutating/setting those attributes for every test.
  • Merc
    Merc over 4 years
    Hi rosscooper and everyone else. I face a similar issue, and I have quite a complex setup of different components, that communicate via vuex. One component watches, like the title says a state property (in my case the return value of a vuex getter). But I don't want to just test if the watcher function is called, I want to ensure that the component's UI shows the correct state, after a watcher has been triggered with a specific value. Very simplified example: I have a vuex getter currentPosition and if they return an empty object, I want to show an additional UI element.
  • oemera
    oemera over 3 years
    This does't work for me. The watcher is never called.