【问题标题】:How to test Vue watcher that watches a computed property from VueX?如何测试从 VueX 监视计算属性的 Vue watcher?
【发布时间】:2018-09-01 01:44:16
【问题描述】:

假设我有以下组件:

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

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

在测试时,我想确保externalDependency.doThing()bar(来自vuex 状态)调用,如下所示:

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 有一个 setComputed 方法,可以让我当前测试它,但我不断收到警告说 setComputed 将很快被删除,我不知道还有什么方法可以测试它:

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

【问题讨论】:

  • 如果你提交了对 Vuex 的更改,计算的属性将通过 Vue 更新。我不知道手表是否会触发,但是设置 Vuex 状态而不是直接更改计算似乎应该可以工作。
  • 但这与单元测试的精神背道而驰——它更像是一个 e2e 测试。您不必在测试 VueX 连接组件时模拟 vueX 功能
  • 您应该尝试阅读以下内容:vue-test-utils.vuejs.org/guides/using-with-vuex.html
  • 鉴于 Vuex 的 mapState 是您组件的输入,这就是您应该模拟的内容。使用 setComputed 无法正确测试您的应用
  • 导入/模拟 VueX 商店进行单元测试没有任何问题。通常你会在beforeEach() 中模拟它们,所以你的it() 块很精简。随着组件的增长,您可能会执行dispatch 并可能观察状态本身,因此为每个测试更改/设置这些属性会变得非常尴尬。

标签: javascript vue.js vuex vue-test-utils


【解决方案1】:

从你想要达到的目标

在测试时,我想确保使用 bar(来自 vuex 状态)调用 externalDependency.doThing(),如下所示:

(这确实是纯粹的单元测试方法),你可以强制改变这个观察者,这基本上是一个函数。 如果计算值或数据值发生更改,则无需跟踪观察者是否正在更改 - 让 Vue 处理它。 因此,要更改已挂载的 Vue 实例中的观察者,只需将其称为

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

bar 是你的观察者的名字。通过这种方式,您将能够测试您要测试的确切功能。

您在问题中提到的关于 vue-test-utils 问题的这条评论 https://github.com/vuejs/vue-test-utils/issues/331#issuecomment-382037200 的想法。

【讨论】:

    【解决方案2】:

    Vue Test Utils 文档指出了一种使用非常简单的 Vuex 存储的不同方法:

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

    【讨论】:

    • 这对我不起作用。观察者永远不会被调用。
    【解决方案3】:

    您将需要在 VueX 实例上使用某种 mutator,是的,这确实会在测试中引入另一个不相关的单元,但是根据您的测试,包括使用 Vuex,这个概念已经被打破了。

    以意想不到的方式修改状态更容易导致与实际使用不同的行为。

    【讨论】:

    • 你好 rosscooper 和其他所有人。我面临一个类似的问题,我有一个相当复杂的不同组件设置,它们通过 vuex 进行通信。一个组件监视,就像标题所说的状态属性(在我的例子中是 vuex getter 的返回值)。但我不想只测试是否调用了 watcher 函数,我想确保在使用特定值触发 watcher 后组件的 UI 显示正确的状态。非常简化的示例:我有一个 vuex getter currentPosition,如果它们返回一个空对象,我想显示一个额外的 UI 元素。
    【解决方案4】:

    您可以直接在源设置值,即 VueX。所以你的 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);
    

    然后在你的 component.spec.js 中你会这样做:

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

    您也可以在 store 上调用 dispatch('setBar', 'baz') 方法并让突变正确发生,而不是直接设置状态。

    NB 为每次挂载重新初始化您的状态很重要(即进行克隆或重新声明)。否则,一个测试可以更改状态,下一个测试将从该脏状态开始,即使包装器已被破坏。

    【讨论】:

      猜你喜欢
      • 2019-10-19
      • 2018-12-29
      • 2020-11-02
      • 1970-01-01
      • 2018-11-28
      • 2018-05-03
      • 1970-01-01
      • 2020-04-02
      • 2019-11-01
      相关资源
      最近更新 更多