Appearance
测试 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 部分(在本例中为 state
和 commit
函数)。
¥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