• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Pinia Store 测试

    从设计上来说,许多地方都会使用 store,所以可能比正常情况更难测试。但幸运的是,这不一定是真的。在测试 store 时,我们需要注意三件事:

    • pinia 实例:没有它,store 就无法运作。
    • actions:大多数时候,它们包含了 store 最复杂的逻辑。如果它们默认就可以被 mocked,那不是很好吗?
    • Plugins:如果你依赖插件,你也必须安装它们进行测试。

    根据测试的内容和方式,我们需要以不同的方式来处理这三个问题:

    • 对 store 单元测试
    • 对组件单元测试
      • 初始 state
      • 自定义 action 的行为
      • 指定 createSpy 函数
      • Mocking getters
      • Pinia 插件
    • 端到端测试
    • 对组件单元测试(Vue 2)


    对 Store 进行单元测试

    要对 Store 进行单元测试,最重要的部分是创建 Pinia 实例:

    // stores/counter.spec.ts
    import { setActivePinia, createPinia } from 'pinia'
    import { useCounter } from '../src/stores/counter'
    
    describe('Counter Store', () => {
      beforeEach(() => {
        // 创建一个新 pinia,并使其处于激活状态,这样它就会被任何 useStore() 调用自动接收而不需要手动传递:`useStore(pinia)`
        setActivePinia(createPinia())
      })
    
      it('increments', () => {
        const counter = useCounter()
        expect(counter.n).toBe(0)
        counter.increment()
        expect(counter.n).toBe(1)
      })
    
      it('increments by amount', () => {
        const counter = useCounter()
        counter.increment(10)
        expect(counter.n).toBe(10)
      })
    })
    

    如果你有使用任何 store 的插件,有一件重要的事情需要知道:首先应用创建 app 实例,然后 pinia 才被创建使用,再然后插件才会被使用。这可以通过创建一个空的或假的应用来解决这个问题:

    import { setActivePinia, createPinia } from 'pinia'
    import { createApp } from 'vue'
    import { somePlugin } from '../src/stores/plugin'
    
    // 和前面一样的代码...
    
    // 测试前你不需要创建应用
    const app = createApp({})
    beforeEach(() => {
      const pinia = createPinia().use(somePlugin)
      app.use(pinia)
      setActivePinia(pinia)
    })
    


    对组件进行单元测试

    这可以通过createTestingPinia()来实现,它返回一个旨在帮助单元测试组件的 Pinia 实例。

    # 安装 @pinia/testing:
    npm i -D @pinia/testing
    

    安装组件时,请确保在测试中创建测试 Pinia:

    import { mount } from '@vue/test-utils'
    import { createTestingPinia } from '@pinia/testing'
    // 引入任何你想要测试的 store
    import { useSomeStore } from '@/stores/myStore'
    
    const wrapper = mount(Counter, {
      global: {
        plugins: [createTestingPinia()],
      },
    })
    
    const store = useSomeStore() // 使用 pinia 的测试实例
    
    // 可直接操作 state
    store.name = 'my new name'
    // 也可以通过 patch 来完成
    store.$patch({ name: 'new name' })
    expect(store.name).toBe('new name')
    
    // action 默认是存根的(stubbed),意味着它们默认不执行其代码。
    // 请看下面的内容来定制这一行为
    store.someAction()
    
    expect(store.someAction).toHaveBeenCalledTimes(1)
    expect(store.someAction).toHaveBeenLastCalledWith()
    

    注意,如果您使用的是 Vue 2,则@vue/test-utils需要稍微不同的配置。


    初始状态

    在创建测试 Pinia 时,您可以通过传递一个initialState对象来设置所有 Store 的初始状态。这个对象将被 pinia 的测试实例用于创建 store 时 patch store。比方说,你想初始化这个 store 的状态:

    import { defineStore } from 'pinia'
    
    const useCounterStore = defineStore('counter', {
      state: () => ({ n: 0 }),
      // ...
    })
    

    由于 Store 名为counter,因此您需要将匹配的对象添加到initialState

    // 在测试中的某处
    const wrapper = mount(Counter, {
      global: {
        plugins: [
          createTestingPinia({
            initialState: {
              counter: { n: 20 }, // 从 20 开始计数,而不是 0
            },
          }),
        ],
      },
    })
    
    const store = useSomeStore() // 使用 pinia 的测试实例!
    store.n // 20
    


    自定义 action 的行为

    除非另有指示,createTestingPinia会存根(stub)出所有的 store action。这样可以让你独立测试你的组件和 store。

    如果你想恢复这种行为,并在测试中正常执行 action,请在调用createTestingPinia时指定stubActions: false

    const wrapper = mount(Counter, {
      global: {
        plugins: [createTestingPinia({ stubActions: false })],
      },
    })
    
    const store = useSomeStore()
    
    // 现在,这个调用将由 store 定义的实现执行。
    store.someAction()
    
    // ...但它仍然被一个 spy 包装着,所以你可以检查调用
    expect(store.someAction).toHaveBeenCalledTimes(1)
    


    指定 createSpy 函数

    当使用 Jest,或 vitest 且设置globals: true时,createTestingPinia会自动使用现有测试框架(jest.fn 或 vitest.fn)的 spy 函数存根(stub)action。如果你使用的是不同的框架,你需要提供一个createSpy选项:

    import sinon from 'sinon'
    
    createTestingPinia({
      createSpy: sinon.spy, // 使用 sinon's spy 包装 action
    })
    


    Mocking getters

    默认情况下,任何 getter 都会像常规用法一样进行计算,但你可以通过将 getter 设置为任何你想要的值来手动强制计算:

    import { defineStore } from 'pinia'
    import { createTestingPinia } from '@pinia/testing'
    
    const useCounter = defineStore('counter', {
      state: () => ({ n: 1 }),
      getters: {
        double: (state) => state.n * 2,
      },
    })
    
    const pinia = createTestingPinia()
    const counter = useCounter(pinia)
    
    counter.double = 3 // getter 仅在测试中可被重写
    
    // 设置为 undefined,以重置默认行为
    // @ts-expect-error: usually it's a number
    counter.double = undefined
    counter.double // 2 (=1 x 2)
    


    Pinia 插件

    如果你有使用任何 pinia 插件,确保在调用createTestingPinia()时传入它们,这样它们就会被正确加载。**不要使用testingPinia.use(MyPlugin)**来加载它们,而应该像正常的 pinia 那样:

    import { createTestingPinia } from '@pinia/testing'
    import { somePlugin } from '../src/stores/plugin'
    
    // 某些测试
    const wrapper = mount(Counter, {
      global: {
        plugins: [
          createTestingPinia({
            stubActions: false,
            plugins: [somePlugin],
          }),
        ],
      },
    })
    


    端到端测试

    对于 pinia,你不需要为端到端测试修改任何代码,这就是端到端测试的含义!也许你想测试 HTTP 请求,但这已经超出了本指南的范围😄。


    对组件单元测试(Vue 2)

    当你使用的是 Vue Test Utils 1 时,请将 Pinia 安装在 localVue 上:

    import { PiniaVuePlugin } from 'pinia'
    import { createLocalVue, mount } from '@vue/test-utils'
    import { createTestingPinia } from '@pinia/testing'
    
    const localVue = createLocalVue()
    localVue.use(PiniaVuePlugin)
    
    const wrapper = mount(Counter, {
      localVue,
      pinia: createTestingPinia(),
    })
    
    const store = useSomeStore() // 使用 pinia 的测试实例!