--- title: Configure Pinia Testing with createTestingPinia and setActivePinia impact: HIGH impactDescription: Missing Pinia configuration causes 'injection Symbol(pinia) not found' errors and failing tests type: gotcha tags: [vue3, testing, pinia, vitest, store, mocking, createTestingPinia] --- # Configure Pinia Testing with createTestingPinia and setActivePinia **Impact: HIGH** - Testing components or composables that use Pinia stores without proper configuration results in "[Vue warn]: injection Symbol(pinia) not found" errors. Tests will fail or behave unexpectedly. Use `@pinia/testing` package with `createTestingPinia` for component tests and `setActivePinia(createPinia())` for unit testing stores directly. ## Task Checklist - [ ] Install `@pinia/testing` as a dev dependency - [ ] Use `createTestingPinia` in component tests with `global.plugins` - [ ] Use `setActivePinia(createPinia())` in `beforeEach` for store unit tests - [ ] Configure `createSpy: vi.fn` when NOT using `globals: true` in Vitest - [ ] Initialize store inside each test to get fresh state - [ ] Use `stubActions: false` when you need real action execution **Incorrect:** ```javascript import { mount } from '@vue/test-utils'; import UserProfile from './UserProfile.vue'; // BAD: Missing Pinia - causes injection error test('displays user name', () => { const wrapper = mount(UserProfile); // ERROR: injection "Symbol(pinia)" not found expect(wrapper.text()).toContain('John'); }); ``` ```javascript import { useUserStore } from '@/stores/user'; // BAD: No active Pinia instance test('user store actions', () => { const store = useUserStore(); // ERROR: no active Pinia store.login('john', 'password'); }); ``` **Correct - Component Testing:** ```javascript import { mount } from '@vue/test-utils'; import { createTestingPinia } from '@pinia/testing'; import { vi } from 'vitest'; import UserProfile from './UserProfile.vue'; import { useUserStore } from '@/stores/user'; // CORRECT: Provide testing pinia with stubbed actions test('displays user name', () => { const wrapper = mount(UserProfile, { global: { plugins: [ createTestingPinia({ createSpy: vi.fn, // Required if not using globals: true initialState: { user: { name: 'John', email: 'john@example.com' }, }, }), ], }, }); expect(wrapper.text()).toContain('John'); }); // CORRECT: Test with stubbed actions (default behavior) test('calls logout action', async () => { const wrapper = mount(UserProfile, { global: { plugins: [createTestingPinia({ createSpy: vi.fn })], }, }); // Get store AFTER mounting with createTestingPinia const store = useUserStore(); await wrapper.find('[data-testid="logout"]').trigger('click'); // Actions are stubbed and wrapped in spies expect(store.logout).toHaveBeenCalled(); }); ``` **Correct - Store Unit Testing:** ```javascript import { describe, it, expect, beforeEach, vi } from 'vitest'; import { setActivePinia, createPinia } from 'pinia'; import { useUserStore } from '@/stores/user'; describe('User Store', () => { beforeEach(() => { // Create fresh Pinia instance for each test setActivePinia(createPinia()); }); it('initializes with empty user', () => { const store = useUserStore(); expect(store.user).toBeNull(); expect(store.isLoggedIn).toBe(false); }); it('updates user on login', async () => { const store = useUserStore(); // Real action executes - not stubbed await store.login('john', 'password'); expect(store.user).toEqual({ name: 'John' }); expect(store.isLoggedIn).toBe(true); }); it('clears user on logout', () => { const store = useUserStore(); store.user = { name: 'John' }; // Set initial state store.logout(); expect(store.user).toBeNull(); }); }); ``` ## Testing with Real Actions vs Stubbed Actions ```javascript import { createTestingPinia } from '@pinia/testing'; // Stubbed actions (default) - for isolation const wrapper = mount(Component, { global: { plugins: [ createTestingPinia({ createSpy: vi.fn, // stubActions: true (default) - actions are mocked }), ], }, }); // Real actions - for integration testing const wrapper = mount(Component, { global: { plugins: [ createTestingPinia({ createSpy: vi.fn, stubActions: false, // Actions execute normally }), ], }, }); ``` ## Mocking Specific Action Implementations ```javascript import { mount } from '@vue/test-utils'; import { createTestingPinia } from '@pinia/testing'; import { vi } from 'vitest'; import { useCartStore } from '@/stores/cart'; test('handles checkout failure', async () => { const wrapper = mount(Checkout, { global: { plugins: [createTestingPinia({ createSpy: vi.fn })], }, }); const cartStore = useCartStore(); // Mock specific action behavior cartStore.checkout.mockRejectedValue(new Error('Payment failed')); await wrapper.find('[data-testid="checkout"]').trigger('click'); await flushPromises(); expect(wrapper.find('.error').text()).toContain('Payment failed'); }); ``` ## Spying on Actions with vi.spyOn ```javascript import { setActivePinia, createPinia } from 'pinia'; import { vi } from 'vitest'; import { useUserStore } from '@/stores/user'; test('tracks action calls', async () => { setActivePinia(createPinia()); const store = useUserStore(); const loginSpy = vi.spyOn(store, 'login'); loginSpy.mockResolvedValue({ success: true }); await store.login('john', 'password'); expect(loginSpy).toHaveBeenCalledWith('john', 'password'); }); ``` ## Testing Store $subscribe ```javascript import { setActivePinia, createPinia } from 'pinia'; import { useUserStore } from '@/stores/user'; test('subscription triggers on state change', () => { setActivePinia(createPinia()); const store = useUserStore(); const callback = vi.fn(); store.$subscribe(callback); store.user = { name: 'John' }; expect(callback).toHaveBeenCalled(); }); ``` ## Reference - [Pinia Testing Guide](https://pinia.vuejs.org/cookbook/testing.html) - [@pinia/testing Package](https://www.npmjs.com/package/@pinia/testing) - [Vue Test Utils - Plugins](https://test-utils.vuejs.org/guide/advanced/plugins.html)