Vue,vuex:如何使用命名空间存储和模拟对组件进行单元测试?

Posted

技术标签:

【中文标题】Vue,vuex:如何使用命名空间存储和模拟对组件进行单元测试?【英文标题】:Vue, vuex: how to unit test a component with namespaced store and mocking? 【发布时间】:2020-08-09 09:03:44 【问题描述】:

我正在使用 vue、vuex 和 vuetify 构建登录组件。我决定在商店中使用命名空间的身份验证模块,这导致了我的问题。

我正在使用 TDD 来解决这个问题。我的 e2e 测试有效。但是我编写了这个单元测试(使用 mockStore),它应该只验证一个正确的动作已经被调度:

describe('Login component', () => 
  let wrapper
  const mockStore = 
    dispatch: jest.fn(),
  

  beforeEach(() => 
    wrapper = mount(Login, 
      localVue,
      mocks:  $store: mockStore ,
      computed: 
        error: () => 'test error',
      ,
      data: () => (
        valid: true
      )
    )
  )

  it('should dispatch login action', async () => 
    wrapper.find('[data-test="username"]').setValue('username')
    wrapper.find('[data-test="password"]').setValue('password')
    await wrapper.vm.$nextTick()
    await wrapper.vm.$nextTick()
    wrapper.find('[data-test="login"]').trigger('click')
    await wrapper.vm.$nextTick()
    expect(mockStore.dispatch).toHaveBeenCalledWith(
      `auth/$LOGIN`,
       username: 'username', password: 'password' 
    )
  )
)

组件仅通过以下方式使用mapActions

...mapActions('auth', [LOGIN])

以及触发它的按钮:

      <v-btn
        color="info"
        @click="login( username, password )"
        data-test="login"
        :disabled="!valid"
      >Login</v-btn>

我得到的错误是:

[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'auth/' of undefined"

如果我将命名空间放在mapActions 中,那么我得到的调度操作名称没有命名空间(duh)并且测试失败:

    - Expected
    + Received

    - "auth/login",
    + "login",

我实际上可以通过如下映射操作来修复它:

...mapActions( login: `auth/$LOGIN` )

但我真的更喜欢使用命名空间版本,因为当我有更多操作时它会变得丑陋。

我正在研究 vuex 源代码,但在尝试访问 _modulesNamespaceMap 时它失败了,但它对我来说太复杂了。

测试这个的最佳方法是什么?我现在应该放弃嘲笑并使用真正的商店吗?

完整的项目可用here 并且与此问题相关的提交是4a7e749d4

【问题讨论】:

【参考方案1】:

对于迁移到 Vue 3 Test Utils 的任何人,请注意上述答案所依赖的 createLocalVue 方法已在 @vue/test-utils 中删除(请参阅 https://next.vue-test-utils.vuejs.org/migration/#no-more-createlocalvue)。

相反,它建议使用来自 Vuex 的 createStore 方法。我能够让命名空间模块像这样工作:

/* ... other imports and setup ... */
import  mount  from "@vue/test-utils";
import Logon from "path/to/your/logon/component";
import  createStore  from "vuex";

describe('Login component', () => 

  const actions = 
    login: jest.fn(),
  ;

  const mockStore = createStore(
    modules: 
      auth: 
        namespaced: true,
        actions,
      ,
    ,
  );

  let wrapper;

  beforeEach(() => 
    wrapper = mount(Login, 
      global: 
        plugins: [mockStore],
      ,
    );
  );

  it('should dispatch login action', async () => 
     /*...test code goes here */
  )
)

【讨论】:

【参考方案2】:

以the example on the vue-test-utils docs 为基础,我认为这应该可行:

/* ... other imports and setup ... */
import Vuex from 'vuex'

describe('Login component', () => 
  let wrapper
  const actions = 
    login: jest.fn(),
  
  const mockStore = new Vuex(
    modules: 
      auth: 
        namespaced: true,
        actions,
      ,
    ,
  )

  beforeEach(() => 
    wrapper = mount(Login, 
      localVue,
      mocks:  $store: mockStore ,
      computed: 
        error: () => 'test error',
      ,
      data: () => (
        valid: true
      )
    )
  )

  it('should dispatch login action', async () => 
    wrapper.find('[data-test="username"]').setValue('username')
    wrapper.find('[data-test="password"]').setValue('password')
    await wrapper.vm.$nextTick()
    await wrapper.vm.$nextTick()
    wrapper.find('[data-test="login"]').trigger('click')
    await wrapper.vm.$nextTick()
    expect(actions.login).toHaveBeenCalled() // <-- pretty sure this will work
    expect(actions.login).toHaveBeenCalledWith( // <-- not as sure about this one
      username: 'username',
      password: 'password',
    )
  )
)

【讨论】:

太棒了。我最终使用了类似的方法,唯一困扰我的是创建一个实际的 Vuex 对象

以上是关于Vue,vuex:如何使用命名空间存储和模拟对组件进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

从模板访问 vue vuex 命名空间的 getter

打字稿:引用 Vuex 存储模块会导致 VueJs 2.5 的命名空间错误

测试 Vue 组件 - 模拟状态和方法

如何为 vuex 命名空间模块状态创建 getter 和 setter

Vue之Vuex的使用

我应该如何构建多用途 Vue 组件以从不同的 Vuex 路径加载数据?