Testing Vue watcher that observes a computed property in VueX: Tips and Tricks

Consider the code snippet below:

import { mapState } from 'vuex';
import externalDependency from '...';

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

During testing, I need to verify that externalDependency.doThing() is invoked with the value of bar (obtained from the vuex state) as follows:

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');
});

Although Vue test-utils currently supports the use of setComputed method for testing, there are warnings indicating that setComputed will be deprecated soon. I am seeking alternative methods to achieve the same level of testing without relying on setComputed:

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

Answer №1

When looking to achieve a specific outcome

While conducting tests, my goal is to confirm that externalDependency.doThing() is invoked with the value "bar" (which is sourced from the vuex state) as follows:

(and following a purely unit testing approach), simply trigger a change in the watcher, which essentially functions as a callback. There's no necessity to monitor the watcher for changes when dealing with computed or data value modifications - let Vue handle that for you. Therefore, to modify a watcher within a mounted Vue instance, just execute it like this:

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

Here, the term bar refers to the specific watcher you are targeting. This method enables you to test the precise functionality that you intend to verify.

This approach was inspired by a comment found at https://github.com/vuejs/vue-test-utils/issues/331#issuecomment-382037200, within a discussion on a vue-test-utils issue, which you had referenced in a previous inquiry.

Answer №2

The documentation for Vue Test Utils introduces a new method that involves using a simple Vuex store setup:

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

// To avoid global Vue instance pollution, a localVue is used
const localVue = createLocalVue();
localVue.use(Vuex);

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

  beforeEach(() => {
    // A new store is created for each test to maintain isolation
    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');
    // Update the state to trigger the watch
    state.bar = 'baz';
    expect(spy).toHaveBeenCalledWith('baz');
  });
})

Answer №3

To update a value directly from the source, such as VueX, you can implement it like this in your store.js:

const state = {
  bar: 'foo',
};
const mutations = {
  SET_BAR: (currentState, payload) => {
    currentState.bar = payload;
  },
};
const actions = {
  setBar: ({ commit }, payload) => {
    commit('SET_BAR', payload);
  },
};

export const mainStore = {
  state,
  mutations,
  actions,
};

export default new Vuex.Store(mainStore);

Next, in your component.spec.js, you would need to follow these steps:

import { mainStore } from '../store';
import Vuex from 'vuex';

//... describe, and other setup functions
it('should call externalDependency.doThing with bar', async () => {
  const localState = {
    bar: 'foo',
  };
  const localStore = new Vuex.Store({
      ...mainStore,
      state: localState,
  });
  const wrapper = mount(Foo, {
    store: localStore,
  });
  const spy = jest.spyOn(externalDependency, 'doThing');
  localStore.state.bar = 'baz';
  await wrapper.vm.$nextTick();
  expect(spy).toHaveBeenCalledWith('baz');
});

Instead of directly updating the state, you can also use the dispatch('setBar', 'baz') method on the store to ensure the mutation takes place correctly.

Note: It's vital to reset your state for each mount (either create a clone or declare it again) to prevent one test from affecting the state for subsequent tests, even if the wrapper was destroyed.

Answer №4

In order to effectively manipulate the VueX instance, incorporating a mutator is necessary. Although this may add an additional element to the testing process, it is important to recognize that including VueX in your tests already shifts the existing concept.

Any alterations made to the state in an unexpected manner can lead to deviations in behavior that may not align with the intended usage.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting problem with Ajax responseText

Can an ajax responseText be received without replacing the existing content? For instance: <div id="content"> <p>Original content</p> </div> Typically, after running an ajax request with a responseText that targets id="conten ...

Applying REGEX on input text in React Native

I'm having trouble getting my regex function to work correctly, so I believe there might be an error in my code. Any assistance would be greatly appreciated. Here is the regex function I am using: let validatePlate = (plate) => { var re = /(^[A ...

What does the error message "Uncaught TypeError: Cannot access property 'style' of an undefined object" mean?

I've been attempting to execute the code below, but I keep encountering an error. I even tried including document.addEventListener('DOMContentLoaded', (event) => yet it still doesn't work. Interestingly, there are no errors if I run ...

Troubleshooting Vue v-for loop triggering click events on multiple items simultaneously

After conducting a thorough search, I haven't found a suitable answer for my issue. The problem I am facing involves a v-for loop with buttons on each item, using VueClipboard2 to copy text. Whenever a button is clicked, some CSS changes are applied t ...

Transformer Class: An object containing properties that are instances of another class

class ClassA { x: number; y: number; sum(): number { return this.x + this.y; } } class ClassB { @Type(() => ClassA) z: {[key: string]: ClassA}; } const b = transformObject(ClassB, obj); const z = b.z[key]; const s = z.s ...

Incorporating the power of ES6 into a pre-existing website using React

I currently have an established website with a page accessible through the URL www.example.com/apps/myApp. The myApp functionality is embedded within an existing HTML page and serves as a utility app. I am interested in learning React, so I see this as a g ...

Choose the initial unordered list within a specific division through Jquery

In a div, there is a ul. Inside a li, there is another ul. The task is to select only the first ul inside the div using jQuery. The HTML markup: <div class="parent"> <div class="clearfix"> <div class="another-div"> <ul cl ...

Troubleshooting date range validation in jQuery

I am facing an issue in my code where I have a set of start and end date fields that need to be validated to ensure the start date comes before the end date. I am currently using the jQuery validation plugin for this purpose. For reference, here is the li ...

The value remains constant until the second button is pressed

I have a button that increments the value of an item. <Button bsStyle="info" bsSize="lg" onClick={this.addItem}> addItem: addItem: function() { this.setState({ towelCount: this.state.towelCount - 2, koalaCount: this.state.koalaCount + 2 ...

Buttoned Up: The Troubling Tale of jQuery and

Seems like this question might be a duplicate, but I'm not bothered. Despite searching on Google, I haven't found a solution. What could be the mistake here? $(document).ready(function() { $("#foo").click(function() { $.ajax({ type ...

React/React Hooks: Want to initiate input validation when a user deselects a checkbox

Currently, my component includes an input field and a checkbox. When the checkbox is checked, it disables the input field and clears any validation errors. However, I want to add functionality so that if the checkbox is unchecked, the input field becomes ...

Interactive Thumbnail Selection for HTML5 Video

Having trouble with creating thumbnails? I managed to solve the cross-domain issue using an html2canvas PHP proxy. No error messages in the Console, but the thumbnails are unfortunately not showing up - they appear transparent or white. Here is a snippet ...

Is there a way to turn off alerts from Aspx files for HTML and CSS?

Dealing with annoying warnings in my aspx files has been a constant struggle. The "CSS Value is not defined" message pops up when I reference CSS files from different projects, causing unnecessary frustration. Even more frustrating are the warnings about i ...

Keep going in the for await loop in NodeJS

My code snippet looks like this: for await (const ele of offCycles) { if ((await MlQueueModel.find({ uuid: ele.uuid })).length !== 0) { continue; } <do something> } I'm curious if it's possible to use a continue st ...

The JavaScript-rendered HTML button is unresponsive

I have a JavaScript function that controls the display of a popup window based on its visibility. The function used to work perfectly, with the close button effectively hiding the window when clicked. However, after making some changes to the code, the clo ...

Adding embedded attributes from a different object

I am facing a challenge with two arrays called metaObjects and justObjects. These arrays consist of objects that share a common property called id. My goal is to merge properties from the objects in these separate arrays into a new array. const metaObje ...

Set the timezone of a Javascript Date to be zero

Is there a way to create a Javascript date without any specific timezone? When I try to do so in Javascript, it automatically sets it to GMT Pacific standard time. let newDate = new Date(new Date().getFullYear(), 0, 2, 0, 0, 0, 0) }, newDate: Sat Feb 01 2 ...

Is there a specific side effect that warrants creating a new Subscription?

Recently, I had a discussion on Stack Overflow regarding RxJS and the best approach for handling subscriptions in a reactive application. The debate was whether it's better to create a subscription for each specific side effect or minimize subscriptio ...

Looking to expand the width of the sub menu to reach the full width of the

Is there a way to use CSS to make a sub-menu start from the left side of the screen instead of starting it below the parent item? nav { margin: 0 auto; text-align: center; } nav ul ul { display: none; } nav ul li:hover > ul { di ...

Struggling to retrieve information from session storage to pass along to a PHP class

Is there a way to fetch the email of the currently logged-in user from sessionStorage and send it to a PHP file? I have tried implementing this, but it seems to be not functioning properly. Could you assist me in resolving this issue? <?php $mongoCl ...