Cafedev rất hân hạnh chia sẻ với các bạn về việc kiểm thử các cửa hàng trong Vuejs. Các cửa hàng thường được sử dụng tại nhiều nơi và có thể làm cho việc kiểm thử trở nên khó khăn hơn nhiều so với cần thiết. Tuy nhiên, may mắn thay, điều này không nhất thiết phải là trường hợp như vậy. Chúng ta cần chăm sóc ba điều khi kiểm thử các Store:…

Theo thiết kế, các Store sẽ được sử dụng ở nhiều nơi và có thể làm cho việc kiểm thử trở nên khó khăn hơn so với những gì nên có. May mắn là điều này không nhất thiết phải xảy ra. Chúng ta cần chú ý đến ba điều khi kiểm thử các store:
– Thể hiện pinia: Store không thể hoạt động mà không có nó.
actions: Phần lớn thời gian, chúng chứa các logic phức tạp nhất của các store của chúng ta. Liệu có tốt không nếu chúng ta tự động mock chúng?
– Plugins: Nếu bạn phụ thuộc vào các plugins, bạn cũng phải cài đặt chúng cho các bài kiểm thử.
Tùy thuộc vào điều gì hoặc cách bạn kiểm thử, chúng ta cần chú ý đến ba điều này theo cách khác nhau.

1. Kiểm thử đơn vị một store

Để kiểm thử đơn vị một store, phần quan trọng nhất là tạo ra một pinia instance:

// stores/counter.spec.ts
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '../src/stores/counter'

describe('Counter Store', () => {
  beforeEach(() => {
    // creates a fresh pinia and makes it active
    // so it's automatically picked up by any useStore() call
    // without having to pass it to it: `useStore(pinia)`
    setActivePinia(createPinia())
  })

  it('increments', () => {
    const counter = useCounterStore()
    expect(counter.n).toBe(0)
    counter.increment()
    expect(counter.n).toBe(1)
  })

  it('increments by amount', () => {
    const counter = useCounterStore()
    counter.increment(10)
    expect(counter.n).toBe(10)
  })
})

Nếu bạn có bất kỳ plugins nào của store, có một điều quan trọng cần biết: plugins sẽ không được sử dụng cho đến khi pinia được cài đặt trong ứng dụng. Điều này có thể được giải quyết bằng cách tạo ra một ứng dụng trống hoặc một ứng dụng giả:

import { setActivePinia, createPinia } from 'pinia'
import { createApp } from 'vue'
import { somePlugin } from '../src/stores/plugin'

// same code as above...

// you don't need to create one app per test
const app = createApp({})
beforeEach(() => {
  const pinia = createPinia().use(somePlugin)
  app.use(pinia)
  setActivePinia(pinia)
})

2. Kiểm thử đơn vị các thành phần

Điều này có thể được thực hiện bằng cách sử dụng createTestingPinia(), một hàm trả về một pinia instance được thiết kế để giúp kiểm thử đơn vị các thành phần.
Bắt đầu bằng cách cài đặt @pinia/testing:

npm i -D @pinia/testing

Và đảm bảo tạo một pinia kiểm thử trong các bài kiểm thử của bạn khi gắn một thành phần:

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
// import any store you want to interact with in tests
import { useSomeStore } from '@/stores/myStore'

const wrapper = mount(Counter, {
  global: {
    plugins: [createTestingPinia()],
  },
})

const store = useSomeStore() // uses the testing pinia!

// state can be directly manipulated
store.name = 'my new name'
// can also be done through patch
store.$patch({ name: 'new name' })
expect(store.name).toBe('new name')

// actions are stubbed by default, meaning they don't execute their code by default.
// See below to customize this behavior.
store.someAction()

expect(store.someAction).toHaveBeenCalledTimes(1)
expect(store.someAction).toHaveBeenLastCalledWith()

Lưu ý rằng nếu bạn đang sử dụng Vue 2, @vue/test-utils yêu cầu một cấu hình khác một chút.

2.1 Trạng thái ban đầu

Bạn có thể đặt trạng thái ban đầu của tất cả các store của bạn khi tạo ra một pinia kiểm thử bằng cách truyền một đối tượng initialState. Đối tượng này sẽ được sử dụng bởi pinia kiểm thử để patch các store khi chúng được tạo ra. Giả sử bạn muốn khởi tạo trạng thái của store này:

import { defineStore } from 'pinia'

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

Vì store có tên là “counter”, bạn cần thêm một đối tượng phù hợp vào initialState:

// somewhere in your test
const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        initialState: {
          counter: { n: 20 }, // start the counter at 20 instead of 0
        },
      }),
    ],
  },
})

const store = useSomeStore() // uses the testing pinia!
store.n // 20

2.2 Tùy chỉnh hành vi của các hành động

createTestingPinia stubs ra tất cả các hành động của store trừ khi được chỉ định khác. Điều này cho phép bạn kiểm thử các thành phần và các store một cách riêng biệt.
Nếu bạn muốn hoàn ngược hành vi này và thực hiện bình thường các hành động của bạn trong quá trình kiểm thử, chỉ định stubActions: false khi gọi createTestingPinia:

const wrapper = mount(Counter, {
  global: {
    plugins: [createTestingPinia({ stubActions: false })],
  },
})

const store = useSomeStore()

// Now this call WILL execute the implementation defined by the store
store.someAction()

// ...but it's still wrapped with a spy, so you can inspect calls
expect(store.someAction).toHaveBeenCalledTimes(1)

2.3 Mock giá trị trả về của một hành động

Hành động được tự động spied nhưng theo kiểu, chúng vẫn là các hành động thông thường. Để có được kiểu chính xác, chúng ta phải triển khai một loại bao gói kiểu tùy chỉnh áp dụng kiểu Mock cho mỗi hành động. Loại này phụ thuộc vào framework kiểm thử bạn đang sử dụng. Dưới đây là một ví dụ với Vitest:

import type { Mock } from 'vitest'
import type { Store, StoreDefinition } from 'pinia'

function mockedStore<TStoreDef extends () => unknown>(
  useStore: TStoreDef
): TStoreDef extends StoreDefinition<
  infer Id,
  infer State,
  infer Getters,
  infer Actions
  ? Store<
      Id,
      State,
      Getters,
      {
        [K in keyof Actions]: Actions[K] extends (
          ...args: infer Args
        ) => infer ReturnT
          ? // 👇 depends on your testing framework
            Mock<Args, ReturnT>
          : Actions[K]
      }
  : ReturnType<TStoreDef> {
  return useStore() as any
}

Điều này có thể được sử dụng trong các bài kiểm thử để có một store được gõ chính xác:

import { mockedStore } from './mockedStore'
import { useSomeStore } from '@/stores/myStore'

const store = mockedStore(useSomeStore)
// typed!
store.someAction.mockResolvedValue('some value')

2.4 Xác định hàm createSpy

Khi sử dụng Jest, hoặc vitest với globals: true, createTestingPinia tự động stubs các hành động bằng hàm spy dựa trên framework kiểm thử hiện tại (jest.fn hoặc vitest.fn). Nếu bạn không sử dụng globals: true hoặc sử dụng một framework khác, bạn sẽ cần cung cấp một tùy chọn createSpy:
code-group

[vitest]
// NOTE: not needed with `globals: true`
import { vi } from 'vitest'

createTestingPinia({
  createSpy: vi.fn,
})
[sinon]
import sinon from 'sinon'

createTestingPinia({
  createSpy: sinon.spy,
})

Bạn có thể tìm thêm ví dụ trong các bài kiểm thử của gói kiểm thử.

2.5 Mocking getters

Theo mặc định, bất kỳ getter nào cũng sẽ được tính toán giống như việc sử dụng bình thường nhưng bạn có thể bắt buộc một giá trị bằng cách đặt getter thành bất cứ điều gì bạn muốn:

import { defineStore } from 'pinia'
import { createTestingPinia } from '@pinia/testing'

const useCounterStore = defineStore('counter', {
  state: () => ({ n: 1 }),
  getters: {
    double: (state) => state.n * 2,
  },
})

const pinia = createTestingPinia()
const counter = useCounterStore(pinia)

counter.double = 3 // 🪄 getters are writable only in tests

// set to undefined to reset the default behavior
// @ts-expect-error: usually it's a number
counter.double = undefined
counter.double // 2 (=1 x 2)

2.6 Các Plugin Pinia

Nếu bạn có bất kỳ plugin pinia nào, hãy đảm bảo chuyển chúng khi gọi createTestingPinia() để chúng được áp dụng đúng cách. Không thêm chúng bằng cách testingPinia.use(MyPlugin) như bạn sẽ làm với một pinia thông thường:

import { createTestingPinia } from '@pinia/testing'
import { somePlugin } from '../src/stores/plugin'

// inside some test
const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        stubActions: false,
        plugins: [somePlugin],
      }),
    ],
  },
})

3. Kiểm thử E2E

Khi nói đến Pinia, bạn không cần phải thay đổi bất cứ điều gì cho các bài kiểm thử E2E, đó là ý nghĩa chính của các bài kiểm thử này! Bạn có thể kiểm tra yêu cầu HTTP, nhưng đó là ngoài phạm vi của hướng dẫn này 😄.

4. Kiểm thử đơn vị các thành phần (Vue 2)

Khi sử dụng Vue Test Utils 1, cài đặt Pinia trên 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() // uses the testing pinia!

Trên đây là những phương pháp và công cụ để kiểm thử store trong Vuejs một cách hiệu quả. Dù việc kiểm thử các cửa hàng có thể gây ra những thách thức, nhưng với những hướng dẫn và tiện ích mà chúng tôi đã chia sẻ, hy vọng rằng bạn sẽ cảm thấy tự tin hơn trong quá trình phát triển ứng dụng của mình. Hãy tiếp tục theo dõi Cafedev để nhận được thêm nhiều hướng dẫn và bài viết hữu ích khác về Vuejs và các công nghệ liên quan.

Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Vuejs từ cơ bản tới nâng cao

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!