How to test Vue watcher that watches a computed property from VueX?
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');
});
})
Related videos on Youtube
yxu296
Updated on June 04, 2022Comments
-
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 withbar
(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:
-
clay about 6 yearsIf 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 about 6 yearsbut 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 over 4 yearsGiven Vuex's
mapState
is an input to your component, that's what you should be mocking. UsingsetComputed
does not test your app correctly -
ierdna over 4 yearsthere's nothing wrong with importing/mocking the VueX store for unit tests. normally you'd mock them in
beforeEach()
, so yourit()
blocks are lean. as your component grows, you'll be doingdispatch
and watching the state itself possibly, so it'll become very awkward mutating/setting those attributes for every test.
-
-
Merc over 4 yearsHi 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 over 3 yearsThis does't work for me. The watcher is never called.