How to test Vuex store in Vue
Testing Vuex stores ensures state management logic works correctly before integrating with components. As the creator of CoreUI with over 10 years of Vue.js experience since 2014, I’ve written comprehensive test suites for Vuex stores in complex applications with hundreds of state mutations and actions. The most effective approach tests each store module in isolation - mutations synchronously, actions with mocked API calls, and getters with sample state. This provides confidence that state management logic is reliable and catches bugs early.
Test Vuex mutations by calling them directly with mock state.
// store/counter.js
export const state = () => ({
count: 0
})
export const mutations = {
increment(state) {
state.count++
},
decrement(state) {
state.count--
},
setCount(state, value) {
state.count = value
}
}
// store/counter.spec.js
import { describe, it, expect } from 'vitest'
import { mutations } from './counter'
describe('Counter mutations', () => {
it('increments count', () => {
const state = { count: 0 }
mutations.increment(state)
expect(state.count).toBe(1)
})
it('decrements count', () => {
const state = { count: 5 }
mutations.decrement(state)
expect(state.count).toBe(4)
})
it('sets count to specific value', () => {
const state = { count: 0 }
mutations.setCount(state, 42)
expect(state.count).toBe(42)
})
})
Mutations are pure functions that modify state. Testing involves creating mock state objects and calling mutations directly. Each test verifies the state changes correctly. This approach is simple and fast since mutations are synchronous.
Testing Vuex Actions
Test actions by mocking the commit function and API calls.
// store/users.js
export const actions = {
async fetchUsers({ commit }) {
const response = await fetch('/api/users')
const users = await response.json()
commit('setUsers', users)
},
async addUser({ commit }, user) {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
})
const newUser = await response.json()
commit('addUser', newUser)
}
}
// store/users.spec.js
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { actions } from './users'
describe('Users actions', () => {
beforeEach(() => {
global.fetch = vi.fn()
})
it('fetches users and commits setUsers', async () => {
const mockUsers = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
global.fetch.mockResolvedValueOnce({
json: async () => mockUsers
})
const commit = vi.fn()
await actions.fetchUsers({ commit })
expect(commit).toHaveBeenCalledWith('setUsers', mockUsers)
})
it('adds user and commits addUser', async () => {
const newUser = { name: 'Bob', email: '[email protected]' }
const createdUser = { id: 3, ...newUser }
global.fetch.mockResolvedValueOnce({
json: async () => createdUser
})
const commit = vi.fn()
await actions.addUser({ commit }, newUser)
expect(commit).toHaveBeenCalledWith('addUser', createdUser)
expect(global.fetch).toHaveBeenCalledWith('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser)
})
})
})
Actions are tested by mocking the context object with a spy for commit. The vi.fn() creates mock functions to track calls. The global.fetch is mocked to avoid real API calls. This verifies actions call the right mutations with correct data.
Testing Vuex Getters
Test getters by passing mock state to getter functions.
// store/products.js
export const state = () => ({
products: [
{ id: 1, name: 'Laptop', price: 1000, inStock: true },
{ id: 2, name: 'Mouse', price: 25, inStock: false },
{ id: 3, name: 'Keyboard', price: 75, inStock: true }
]
})
export const getters = {
availableProducts: (state) => {
return state.products.filter(p => p.inStock)
},
productCount: (state) => {
return state.products.length
},
expensiveProducts: (state) => (minPrice) => {
return state.products.filter(p => p.price >= minPrice)
}
}
// store/products.spec.js
import { describe, it, expect } from 'vitest'
import { getters } from './products'
describe('Products getters', () => {
const state = {
products: [
{ id: 1, name: 'Laptop', price: 1000, inStock: true },
{ id: 2, name: 'Mouse', price: 25, inStock: false },
{ id: 3, name: 'Keyboard', price: 75, inStock: true }
]
}
it('returns available products', () => {
const result = getters.availableProducts(state)
expect(result).toHaveLength(2)
expect(result[0].name).toBe('Laptop')
expect(result[1].name).toBe('Keyboard')
})
it('returns product count', () => {
const result = getters.productCount(state)
expect(result).toBe(3)
})
it('returns expensive products above threshold', () => {
const result = getters.expensiveProducts(state)(100)
expect(result).toHaveLength(1)
expect(result[0].name).toBe('Laptop')
})
})
Getters are tested by calling them with mock state. For getters that return functions (like expensiveProducts), call the getter first then call the returned function. This verifies computed values derive correctly from state.
Testing Complete Store Module
Test an entire store module including state, mutations, actions, and getters.
// store/cart.js
export const state = () => ({
items: [],
total: 0
})
export const mutations = {
addItem(state, item) {
state.items.push(item)
state.total += item.price
},
removeItem(state, itemId) {
const index = state.items.findIndex(i => i.id === itemId)
if (index !== -1) {
state.total -= state.items[index].price
state.items.splice(index, 1)
}
},
clearCart(state) {
state.items = []
state.total = 0
}
}
export const actions = {
async checkout({ state, commit }) {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: state.items })
})
if (response.ok) {
commit('clearCart')
return true
}
return false
}
}
export const getters = {
itemCount: (state) => state.items.length,
isEmpty: (state) => state.items.length === 0
}
// store/cart.spec.js
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { state, mutations, actions, getters } from './cart'
describe('Cart store', () => {
describe('mutations', () => {
it('adds item to cart', () => {
const testState = state()
const item = { id: 1, name: 'Product', price: 50 }
mutations.addItem(testState, item)
expect(testState.items).toHaveLength(1)
expect(testState.total).toBe(50)
})
it('removes item from cart', () => {
const testState = {
items: [{ id: 1, name: 'Product', price: 50 }],
total: 50
}
mutations.removeItem(testState, 1)
expect(testState.items).toHaveLength(0)
expect(testState.total).toBe(0)
})
})
describe('actions', () => {
beforeEach(() => {
global.fetch = vi.fn()
})
it('clears cart on successful checkout', async () => {
global.fetch.mockResolvedValueOnce({ ok: true })
const testState = {
items: [{ id: 1, name: 'Product', price: 50 }],
total: 50
}
const commit = vi.fn()
const result = await actions.checkout({ state: testState, commit })
expect(result).toBe(true)
expect(commit).toHaveBeenCalledWith('clearCart')
})
})
describe('getters', () => {
it('returns item count', () => {
const testState = {
items: [{ id: 1 }, { id: 2 }],
total: 100
}
expect(getters.itemCount(testState)).toBe(2)
})
it('returns isEmpty as true for empty cart', () => {
const testState = { items: [], total: 0 }
expect(getters.isEmpty(testState)).toBe(true)
})
})
})
This comprehensive test covers all parts of the store module. Each section tests a specific concern independently. Organizing tests by type (mutations, actions, getters) makes the test file easy to navigate and maintain.
Testing Store with Vuex Store Instance
Test the store as a whole by creating a real Vuex store instance.
import { describe, it, expect, beforeEach } from 'vitest'
import { createStore } from 'vuex'
import { state, mutations, actions, getters } from './cart'
describe('Cart store integration', () => {
let store
beforeEach(() => {
store = createStore({
state,
mutations,
actions,
getters
})
})
it('handles complete cart workflow', () => {
expect(store.getters.isEmpty).toBe(true)
store.commit('addItem', { id: 1, name: 'Product', price: 50 })
expect(store.getters.itemCount).toBe(1)
expect(store.state.total).toBe(50)
store.commit('addItem', { id: 2, name: 'Another', price: 30 })
expect(store.getters.itemCount).toBe(2)
expect(store.state.total).toBe(80)
store.commit('removeItem', 1)
expect(store.getters.itemCount).toBe(1)
expect(store.state.total).toBe(30)
})
})
Integration tests create a real store instance and test multiple operations together. This verifies that mutations, actions, and getters work correctly when combined. Use integration tests for critical workflows that involve multiple store operations.
Best Practice Note
This is the same Vuex testing approach we use in CoreUI Vue projects to ensure reliable state management. Test mutations and getters first since they’re simple and fast. Mock all external dependencies in action tests to keep tests predictable and fast. For large stores, split modules into separate test files. Consider migrating from Vuex to Pinia for new projects - Pinia has better TypeScript support and simpler testing APIs. For testing components that use stores, see our guide on how to test Vue components with Vue Test Utils.



