跳至内容

测试 Vuex

Vuex 只是一个实现细节;测试使用 Vuex 的组件不需要特殊处理。也就是说,有一些技巧可以使您的测试更容易阅读和编写。我们将在本文中介绍这些技巧。

本指南假设您熟悉 Vuex。Vuex 4 是与 Vue.js 3 一起使用的版本。阅读文档 此处

一个简单的例子

这是一个简单的 Vuex 商店,以及一个依赖于 Vuex 商店存在的组件

js
import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      count: 0
    }
  },
  mutations: {
    increment(state: any) {
      state.count += 1
    }
  }
})

该商店只存储一个计数,当提交 increment 变异时,它会增加计数。这是我们将要测试的组件

js
const App = {
  template: `
    <div>
      <button @click="increment" />
      Count: {{ count }}
    </div>
  `,
  computed: {
    count() {
      return this.$store.state.count
    }
  },
  methods: {
    increment() {
      this.$store.commit('increment')
    }
  }
}

使用真实的 Vuex 商店进行测试

为了完全测试该组件和 Vuex 商店是否正常工作,我们将点击 <button> 并断言计数已增加。在您的 Vue 应用程序中,通常在 main.js 中,您会像这样安装 Vuex

js
const app = createApp(App)
app.use(store)

这是因为 Vuex 是一个插件。插件通过调用 app.use 并传入插件来应用。

Vue 测试工具允许您也安装插件,使用 global.plugins 挂载选项。

js
import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      count: 0
    }
  },
  mutations: {
    increment(state: any) {
      state.count += 1
    }
  }
})

test('vuex', async () => {
  const wrapper = mount(App, {
    global: {
      plugins: [store]
    }
  })

  await wrapper.find('button').trigger('click')

  expect(wrapper.html()).toContain('Count: 1')
})

安装插件后,我们使用 trigger 点击按钮并断言 count 已增加。这种涵盖不同系统(在本例中为组件和商店)之间交互的测试被称为集成测试。

使用模拟商店进行测试

相反,单元测试可能会隔离并分别测试组件和商店。如果您有一个具有复杂商店的非常大的应用程序,这可能很有用。对于这种情况,您可以使用 global.mocks 模拟您感兴趣的商店部分

js
test('vuex using a mock store', async () => {
  const $store = {
    state: {
      count: 25
    },
    commit: jest.fn()
  }

  const wrapper = mount(App, {
    global: {
      mocks: {
        $store
      }
    }
  })

  expect(wrapper.html()).toContain('Count: 25')
  await wrapper.find('button').trigger('click')
  expect($store.commit).toHaveBeenCalled()
})

我们没有使用真实的 Vuex 商店并通过 global.plugins 安装它,而是创建了自己的模拟商店,只实现了组件中使用的 Vuex 部分(在本例中为 statecommit 函数)。

虽然单独测试商店可能看起来很方便,但请注意,如果您破坏了 Vuex 商店,它不会向您发出任何警告。仔细考虑您是否要模拟 Vuex 商店,或者使用真实的商店,并了解权衡取舍。

隔离测试 Vuex

您可能希望完全隔离地测试 Vuex 变异或操作,尤其是当它们很复杂时。您不需要 Vue 测试工具来执行此操作,因为 Vuex 商店只是普通的 JavaScript。以下是如何在没有 Vue 测试工具的情况下测试 increment 变异

js
test('increment mutation', () => {
  const store = createStore({
    state: {
      count: 0
    },
    mutations: {
      increment(state) {
        state.count += 1
      }
    }
  })

  store.commit('increment')

  expect(store.state.count).toBe(1)
})

预设 Vuex 状态

有时,让 Vuex 商店处于特定状态以进行测试可能很有用。除了 global.mocks 之外,您可以使用的一种有用技术是创建一个包装 createStore 的函数,并接受一个参数来播种初始状态。在本例中,我们扩展了 increment 以接受一个额外的参数,该参数将添加到 state.count 中。如果没有提供,我们只将 state.count 增加 1。

js
const createVuexStore = (initialState) =>
  createStore({
    state: {
      count: 0,
      ...initialState
    },
    mutations: {
      increment(state, value = 1) {
        state.count += value
      }
    }
  })

test('increment mutation without passing a value', () => {
  const store = createVuexStore({ count: 20 })
  store.commit('increment')
  expect(store.state.count).toBe(21)
})

test('increment mutation with a value', () => {
  const store = createVuexStore({ count: -10 })
  store.commit('increment', 15)
  expect(store.state.count).toBe(5)
})

通过创建一个接受初始状态的 createVuexStore 函数,我们可以轻松地设置初始状态。这使我们能够测试所有边缘情况,同时简化我们的测试。

Vue 测试手册 有更多关于测试 Vuex 的示例。注意:这些示例适用于 Vue.js 2 和 Vue 测试工具 v1。想法和概念是相同的,Vue 测试手册将在不久的将来更新为 Vue.js 3 和 Vue 测试工具 2。

使用组合 API 进行测试

使用组合 API 时,可以通过 useStore 函数访问 Vuex。 在此处阅读更多信息

useStore 可以与可选且唯一的注入键一起使用,如所讨论的 Vuex 文档中

它看起来像这样

js
import { createStore } from 'vuex'
import { createApp } from 'vue'

// create a globally unique symbol for the injection key
const key = Symbol()

const App = {
  setup () {
    // use unique key to access store
    const store = useStore(key)
  }
}

const store = createStore({ /* ... */ })
const app = createApp({ /* ... */ })

// specify key as second argument when calling app.use(store)
app.use(store, key)

为了避免每次使用 useStore 时重复键参数传递,Vuex 文档建议将该逻辑提取到一个辅助函数中,并重用该函数而不是默认的 useStore 函数。 在此处阅读更多信息。使用 Vue 测试工具提供商店的方法取决于在组件中使用 useStore 函数的方式。

测试使用 useStore 但没有注入键的组件

如果没有注入键,商店数据可以通过全局 provide 挂载选项注入到组件中。注入商店的名称必须与组件中的名称相同,例如“store”。

提供未键入的 useStore 的示例

js
import { createStore } from 'vuex'

const store = createStore({
  // ...
})

const wrapper = mount(App, {
  global: {
    provide: {
      store: store
    },
  },
})

测试使用 useStore 且具有注入键的组件

当使用具有注入键的商店时,之前的方法将不起作用。商店实例不会从 useStore 返回。为了访问正确的商店,需要提供标识符。

它必须是传递给组件 setup 函数中的 useStore 或自定义辅助函数中的 useStore 的确切键。由于 JavaScript 符号是唯一的,无法重新创建,因此最好从真实商店导出键。

您可以使用 global.provide 提供正确的键来注入商店,或者使用 global.plugins 安装商店并指定键

使用 global.provide 提供键入的 useStore

js
// store.js
export const key = Symbol()
js
// app.spec.js
import { createStore } from 'vuex'
import { key } from './store'

const store = createStore({ /* ... */ })

const wrapper = mount(App, {
  global: {
    provide: {
      [key]: store
    },
  },
})

使用 global.plugins 提供键入的 useStore

js
// store.js
export const key = Symbol()
js
// app.spec.js
import { createStore } from 'vuex'
import { key } from './store'

const store = createStore({ /* ... */ })

const wrapper = mount(App, {
  global: {
    // to pass options to plugins, use the array syntax.
    plugins: [[store, key]]
  },
})

结论

  • 使用 global.plugins 将 Vuex 安装为插件
  • 使用 global.mocks 模拟全局对象(如 Vuex)以用于高级用例
  • 考虑隔离测试复杂的 Vuex 变异和操作
  • 使用接受参数来设置特定测试场景的函数包装 createStore