Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions packages/docs/cookbook/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,78 @@ store.someAction()
expect(store.someAction).toHaveBeenCalledTimes(1)
```

### Selective action stubbing

Sometimes you may want to stub only specific actions while allowing others to execute normally. You can achieve this by passing an array of action names to the `stubActions` option:

```js
// Only stub the 'increment' and 'reset' actions
const wrapper = mount(Counter, {
global: {
plugins: [
createTestingPinia({
stubActions: ['increment', 'reset']
})
],
},
})

const store = useSomeStore()

// These actions will be stubbed (not executed)
store.increment() // stubbed
store.reset() // stubbed

// Other actions will execute normally but still be spied
store.fetchData() // executed normally
expect(store.fetchData).toHaveBeenCalledTimes(1)
```

For more complex scenarios, you can pass a function that receives the action name and store instance, and returns whether the action should be stubbed:

```js
// Stub actions based on custom logic
const wrapper = mount(Counter, {
global: {
plugins: [
createTestingPinia({
stubActions: (actionName, store) => {
// Stub all actions that start with 'set'
if (actionName.startsWith('set')) return true

// Stub actions based on initial store state
if (store.isPremium) return false

return true
}
})
],
},
})

const store = useSomeStore()

// Actions starting with 'set' are stubbed
store.setValue(42) // stubbed

// Other actions may execute based on the initial store state
store.fetchData() // executed or stubbed based on initial store.isPremium
```

::: tip
- An empty array `[]` means no actions will be stubbed (all actions execute normally)
- The function is evaluated once at store setup time, receiving the store instance in its initial state
:::

You can also manually mock specific actions after creating the store:

```ts
const store = useSomeStore()
vi.spyOn(store, 'increment').mockImplementation(() => {})
// or if using testing pinia with stubbed actions
store.increment.mockImplementation(() => {})
```

### Mocking the returned value of an action

Actions are automatically spied but type-wise, they are still the regular actions. In order to get the correct type, we must implement a custom type-wrapper that applies the `Mock` type to each action. **This type depends on the testing framework you are using**. Here is an example with Vitest:
Expand Down
72 changes: 71 additions & 1 deletion packages/docs/zh/cookbook/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,77 @@ store.someAction()
expect(store.someAction).toHaveBeenCalledTimes(1)
```

<!-- TODO: translation -->
### 选择性 action 存根 %{#selective-action-stubbing}%

有时你可能只想存根特定的 action,而让其他 action 正常执行。你可以通过向 `stubActions` 选项传递一个 action 名称数组来实现:

```js
// 只存根 'increment' 和 'reset' action
const wrapper = mount(Counter, {
global: {
plugins: [
createTestingPinia({
stubActions: ['increment', 'reset']
})
],
},
})

const store = useSomeStore()

// 这些 action 将被存根(不执行)
store.increment() // 存根
store.reset() // 存根

// 其他 action 将正常执行但仍被监听
store.fetchData() // 正常执行
expect(store.fetchData).toHaveBeenCalledTimes(1)
```

对于更复杂的场景,你可以传递一个函数,该函数接收 action 名称和 store 实例,并返回是否应该存根该 action:

```js
// 基于自定义逻辑存根 action
const wrapper = mount(Counter, {
global: {
plugins: [
createTestingPinia({
stubActions: (actionName, store) => {
// 存根所有以 'set' 开头的 action
if (actionName.startsWith('set')) return true

// 根据初始 store 状态存根 action
if (store.isPremium) return false

return true
}
})
],
},
})

const store = useSomeStore()

// 以 'set' 开头的 action 被存根
store.setValue(42) // 存根

// 其他 action 可能根据初始 store 状态执行
store.fetchData() // 根据初始 store.isPremium 执行或存根
```

::: tip
- 空数组 `[]` 表示不存根任何 action(所有 action 正常执行)
- 函数在 store 设置时被评估一次,接收处于初始状态的 store 实例
:::

你也可以在创建 store 后手动模拟特定的 action:

```ts
const store = useSomeStore()
vi.spyOn(store, 'increment').mockImplementation(() => {})
// 或者如果使用带有存根 action 的测试 pinia
store.increment.mockImplementation(() => {})
```

### Mocking the returned value of an action

Expand Down
162 changes: 162 additions & 0 deletions packages/testing/src/testing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ describe('Testing', () => {
increment(amount = 1) {
this.n += amount
},
decrement() {
this.n--
},
setValue(newValue: number) {
this.n = newValue
},
},
})

Expand All @@ -35,6 +41,12 @@ describe('Testing', () => {
function increment(amount = 1) {
n.value += amount
}
function decrement() {
n.value--
}
function setValue(newValue: number) {
n.value = newValue
}
function $reset() {
n.value = 0
}
Expand All @@ -45,6 +57,8 @@ describe('Testing', () => {
double,
doublePlusOne,
increment,
decrement,
setValue,
$reset,
}
})
Expand Down Expand Up @@ -326,6 +340,154 @@ describe('Testing', () => {
storeToRefs(store)
expect(store.doubleComputedCallCount).toBe(0)
})

describe('selective action stubbing', () => {
it('stubs only actions in array', () => {
setActivePinia(
createTestingPinia({
stubActions: ['increment', 'setValue'],
createSpy: vi.fn,
})
)

const store = useStore()

// Actions in array should be stubbed (not execute)
store.increment()
expect(store.n).toBe(0) // Should not change
expect(store.increment).toHaveBeenCalledTimes(1)

store.setValue(42)
expect(store.n).toBe(0) // Should not change
expect(store.setValue).toHaveBeenCalledTimes(1)
expect(store.setValue).toHaveBeenLastCalledWith(42)

// Actions not in array should execute normally but still be spied
store.decrement()
expect(store.n).toBe(-1) // Should change
expect(store.decrement).toHaveBeenCalledTimes(1)
})

it('handles empty array (same as false)', () => {
setActivePinia(
createTestingPinia({
stubActions: [],
createSpy: vi.fn,
})
)

const store = useStore()

// All actions should execute normally
store.increment()
expect(store.n).toBe(1) // Should change
expect(store.increment).toHaveBeenCalledTimes(1)

store.setValue(42)
expect(store.n).toBe(42) // Should change
expect(store.setValue).toHaveBeenCalledTimes(1)
})

it('handles non-existent action names gracefully', () => {
setActivePinia(
createTestingPinia({
stubActions: ['increment', 'nonExistentAction'],
createSpy: vi.fn,
})
)

const store = useStore()

// Should work normally despite non-existent action in array
store.increment()
expect(store.n).toBe(0) // Should not change
expect(store.increment).toHaveBeenCalledTimes(1)

store.setValue(42)
expect(store.n).toBe(42) // Should change (not in array)
expect(store.setValue).toHaveBeenCalledTimes(1)
})

it('stubs actions based on function predicate', () => {
setActivePinia(
createTestingPinia({
stubActions: (actionName) =>
actionName.startsWith('set') || actionName === 'decrement',
createSpy: vi.fn,
})
)

const store = useStore()

// setValue should be stubbed (starts with 'set')
store.setValue(42)
expect(store.n).toBe(0) // Should not change
expect(store.setValue).toHaveBeenCalledTimes(1)

// increment should execute (doesn't match predicate)
store.increment()
expect(store.n).toBe(1) // Should change
expect(store.increment).toHaveBeenCalledTimes(1)

// decrement should be stubbed (matches predicate)
store.decrement()
expect(store.n).toBe(1) // Should not change (stubbed)
expect(store.decrement).toHaveBeenCalledTimes(1)
})

it('function predicate receives correct store instance', () => {
const predicateSpy = vi.fn(() => false)

setActivePinia(
createTestingPinia({
stubActions: predicateSpy,
createSpy: vi.fn,
})
)

const store = useStore()

expect(predicateSpy).toHaveBeenCalledWith('increment', store)
})

it('can stub all actions (default)', () => {
setActivePinia(
createTestingPinia({
stubActions: true,
createSpy: vi.fn,
})
)

const store = useStore()

store.increment()
expect(store.n).toBe(0) // Should not change
expect(store.increment).toHaveBeenCalledTimes(1)

store.setValue(42)
expect(store.n).toBe(0) // Should not change
expect(store.setValue).toHaveBeenCalledTimes(1)
})

it('can not stub any action', () => {
setActivePinia(
createTestingPinia({
stubActions: false,
createSpy: vi.fn,
})
)

const store = useStore()

store.increment()
expect(store.n).toBe(1) // Should change
expect(store.increment).toHaveBeenCalledTimes(1)

store.setValue(42)
expect(store.n).toBe(42) // Should change
expect(store.setValue).toHaveBeenCalledTimes(1)
})
})
}

it('works with no actions', () => {
Expand Down
Loading