Skip to content

测试 Vuex

¥Testing Vuex

Vuex 只是一个实现细节;使用 Vuex 测试组件不需要特殊处理。也就是说,有一些技术可能会使你的测试更易于阅读和编写。我们将在这里看看这些。

¥Vuex is just an implementation detail; no special treatment is required for testing components using Vuex. That said, there are some techniques that might make your tests easier to read and write. We will look at those here.

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

¥This guide assumes you are familiar with Vuex. Vuex 4 is the version that works with Vue.js 3. Read the docs here.

一个简单的例子

¥A Simple Example

这是一个简单的 Vuex 存储,以及一个依赖于 Vuex 存储的组件:

¥Here is a simple Vuex store, and a component that relies on a Vuex store being present:

js
import { createStore } from 'vuex'

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

存储只是存储一个计数,并在提交 increment 突变时增加计数。这是我们将要测试的组件:

¥The store simply stores a count, increasing it when the increment mutation is committed. This is the component we will be testing:

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 存储进行测试

¥Testing with a Real Vuex Store

为了全面测试该组件和 Vuex 存储是否正常工作,我们将单击 <button> 并断言计数增加。在你的 Vue 应用中,通常是在 main.js 中,你可以像这样挂载 Vuex:

¥To fully test that this component and the Vuex store are working, we will click on the <button> and assert the count is increased. In your Vue applications, usually in main.js, you install Vuex like this:

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

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

¥This is because Vuex is a plugin. Plugins are applied by calling app.use and passing in the plugin.

Vue Test Utils 还允许你使用 global.plugins 挂载选项挂载插件。

¥Vue Test Utils allows you to install plugins as well, using the global.plugins mounting option.

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 增加。这种涵盖不同系统(在本例中为组件和存储)之间的交互的测试称为集成测试。

¥After installing the plugin, we use trigger to click the button and assert that count is increased. This kind of test, that covers the interaction between different systems (in this case, the Component and the store), is known as an integration test.

使用模拟存储进行测试

¥Testing with a Mock Store

相反,单元测试可能会单独隔离和测试组件和存储。如果你有一个非常大的应用和复杂的存储,这会很有用。对于此用例,你可以使用 global.mocks 模拟存储中你感兴趣的部分:

¥In contrast, a unit test might isolate and test the component and the store separately. This can be useful if you have a very large application with a complex store. For this use case, you can mock the parts of the store you are interested in using 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 函数)。

¥Instead of using a real Vuex store and installing it via global.plugins, we created our own mock store, only implementing the parts of Vuex used in the component (in this case, the state and commit functions).

虽然单独测试存储看起来很方便,但请注意,如果你破坏 Vuex 存储,它不会给你任何警告。如果你想模拟 Vuex 存储还是使用真实的存储,请仔细考虑,并了解权衡。

¥While it might seem convenient to test the store in isolation, notice that it won't give you any warning if you break your Vuex store. Consider carefully if you want to mock the Vuex store, or use a real one, and understand the trade-offs.

隔离测试 Vuex

¥Testing Vuex in Isolation

你可能想完全隔离地测试你的 Vuex 突变或操作,尤其是当它们很复杂时。为此,你不需要 Vue Test Utils,因为 Vuex 存储只是常规 JavaScript。以下是在没有 Vue Test Utils 的情况下测试 increment 突变的方法:

¥You may want to test your Vuex mutations or actions in total isolation, especially if they are complex. You don't need Vue Test Utils for this, since a Vuex store is just regular JavaScript. Here's how you might test the increment mutation without Vue Test Utils:

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 状态

¥Presetting the Vuex State

有时,让 Vuex 存储处于特定状态以进行测试可能很有用。除了 global.mocks 之外,你可以使用的一项有用技术是创建一个封装 createStore 并采用参数来设定初始状态的函数。在此示例中,我们扩展 increment 以获取附加参数,该参数将添加到 state.count 上。如果未提供,我们只需将 state.count 加 1。

¥Sometimes it can be useful to have the Vuex store in a specific state for a test. One useful technique you can use, other than global.mocks, is to create a function that wraps createStore and takes an argument to seed the initial state. In this example we extend increment to take an additional argument, which will be added on to the state.count. If that is not provided, we just increment state.count by 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 函数,我们可以轻松设置初始状态。这使我们能够测试所有边缘情况,同时简化我们的测试。

¥By creating a createVuexStore function that takes an initial state, we can easily set the initial state. This allows us to test all of the edge cases, while simplifying our tests.

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

¥The Vue Testing Handbook has more examples for testing Vuex. Note: the examples pertain to Vue.js 2 and Vue Test Utils v1. The ideas and concepts are the same, and the Vue Testing Handbook will be updated for Vue.js 3 and Vue Test Utils 2 in the near future.

使用组合 API 进行测试

¥Testing using the Composition API

使用 Composition API 时,通过 useStore 函数访问 Vuex。在这里阅读更多相关信息

¥Vuex is accessed via a useStore function when using the Composition API. Read more about it here.

useStore 可以与 在 Vuex 文档中 中讨论的可选且唯一的注入密钥一起使用。

¥useStore can be used with an optional and unique injection key as discussed in the Vuex documentation.

它看起来像这样:

¥It looks like this:

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 Test Utils 提供存储的方法取决于组件中使用 useStore 函数的方式。

¥To avoid repeating the key parameter passing whenever useStore is used, the Vuex documentation recommends extracting that logic into a helper function and reuse that function instead of the default useStore function. Read more about it here. The approach providing a store using Vue Test Utils depends on the way the useStore function is used in the component.

在没有注入密钥的情况下测试使用 useStore 的组件

¥Testing Components that Utilize useStore without an Injection Key

如果没有注入密钥,则可以通过全局 provide 挂载选项将存储数据注入到组件中。注入的存储名称必须与组件中的名称相同,例如 "store"。

¥Without an injection key, the store data can just be injected into the component via the global provide mounting option. The name of the injected store must be the same as the one in the component, e.g. "store".

提供未加密的 useStore 的示例

¥Example for providing the unkeyed useStore

js
import { createStore } from 'vuex'

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

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

通过注入密钥测试使用 useStore 的组件

¥Testing Components that Utilize useStore with an Injection Key

当使用带有注入密钥的存储时,以前的方法将不起作用。存储实例不会从 useStore 返回。为了访问正确的存储,需要提供标识符。

¥When using the store with an injection key ,the previous approach won't work. The store instance won't be returned from useStore. In order to access the correct store the identifier needs to be provided.

它需要是传递到组件 setup 函数中的 useStore 或传递到自定义辅助程序函数中的 useStore 的确切密钥。由于 JavaScript 符号是唯一的并且无法重新创建,因此最好从真实存储中导出密钥。

¥It needs to be the exact key that is passed to useStore in the setup function of the component or to useStore within the custom helper function. Since JavaScript symbols are unique and can't be recreated, it is best to export the key from the real store.

你可以使用 global.provide 和正确的密钥来注入存储,或使用 global.plugins 挂载存储并指定密钥:

¥You can either use global.provide with the correct key to inject the store, or global.plugins to install the store and specify the key:

使用 global.provide 提供使用密钥的 useStore

¥Providing the Keyed useStore using global.provide

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

¥Providing the Keyed useStore using global.plugins

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]]
  },
})

结论

¥Conclusion

  • 使用 global.plugins 挂载 Vuex 作为插件

    ¥Use global.plugins to install Vuex as a plugin

  • 使用 global.mocks 模拟全局对象,例如 Vuex,用于高级用例

    ¥Use global.mocks to mock a global object, such as Vuex, for advanced use cases

  • 考虑单独测试复杂的 Vuex 突变和操作

    ¥Consider testing complex Vuex mutations and actions in isolation

  • 用一个函数封装 createStore,该函数采用参数来设置特定的测试场景

    ¥Wrap createStore with a function that takes an argument to set up specific test scenarios

Vue Test Utils 中文网 - 粤ICP备13048890号