Mocking Composables, Stores, and APIs
vi.mock(), mocking useFetch, mocking Pinia stores, mocking router in tests.
Mocking Composables, Stores, and APIs is a free Vue Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Vue Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Mock?
Unit tests should isolate the component under test. Real network calls, real stores, and heavy composables make tests slow and flaky. Mocking replaces those dependencies with controllable fakes.
vi.fn: A Mock Function
vi.fn() creates a tracked function. You can inspect how it was called and control what it returns. It is the building block of all mocking.
import { vi } from "vitest"
const onSave = vi.fn()
onSave("draft")
expect(onSave).toHaveBeenCalledWith("draft")
expect(onSave).toHaveBeenCalledTimes(1)Setting Return Values
Control a mock’s output with mockReturnValue (sync) or mockResolvedValue (a resolved promise) for async code.
const getId = vi.fn().mockReturnValue(42)
expect(getId()).toBe(42)
const fetchUser = vi.fn().mockResolvedValue({ name: "Ada" })
// await fetchUser() => { name: "Ada" }Mocking a Composable Module
vi.mock(path) replaces an entire module. Provide a factory returning fake exports. Use it to stub a composable so the component gets predictable data.
import { vi } from "vitest"
vi.mock("@/composables/useUser", () => ({
useUser: () => ({
user: { value: { name: "Ada" } },
isLoading: { value: false },
}),
}))vi.mock Hoisting
vi.mock calls are hoisted to the top of the file before imports run. That is why the factory cannot reference outer variables defined later — declare any helper inside the factory or via vi.hoisted.
// This runs before imports regardless of position.
vi.mock("@/api", () => ({ getPosts: vi.fn() }))
import { getPosts } from "@/api" // already the mockTesting Pinia Stores
Components that use a Pinia store need a store in tests. @pinia/testing provides createTestingPinia, which gives you a store whose actions are mocked by default.
// terminal: npm install -D @pinia/testing
import { createTestingPinia } from "@pinia/testing"Installing the Testing Pinia
Pass the testing Pinia through the global plugins option when mounting. You can seed initial state too.
import { mount } from "@vue/test-utils"
import { createTestingPinia } from "@pinia/testing"
const wrapper = mount(Profile, {
global: {
plugins: [createTestingPinia({
initialState: { user: { name: "Ada" } },
})],
},
})Asserting Store Actions
By default actions are stubbed (not run) so you can assert they were dispatched. Grab the store and check the spy.
import { useUserStore } from "@/stores/user"
const store = useUserStore()
await wrapper.get("button.logout").trigger("click")
expect(store.logout).toHaveBeenCalled()Spying on fetch
Rather than replacing a whole module, you can spy on a global. vi.spyOn(global, "fetch") wraps the real fetch so you can override and inspect it.
const spy = vi.spyOn(global, "fetch").mockResolvedValue({
ok: true,
json: async () => ({ items: [1, 2, 3] }),
})A Full Fetch Test
Mock fetch, trigger the action, flush promises, assert on rendered data and that fetch was called with the right URL.
it("loads items", async () => {
vi.spyOn(global, "fetch").mockResolvedValue({
ok: true,
json: async () => ({ items: ["a", "b"] }),
})
const wrapper = mount(List)
await flushPromises()
expect(global.fetch).toHaveBeenCalledWith("/api/items")
expect(wrapper.findAll("li")).toHaveLength(2)
})Resetting Mocks Between Tests
Shared mocks accumulate call history. Reset them in beforeEach (or enable clearMocks in config) so one test does not leak into another.
import { beforeEach, vi } from "vitest"
beforeEach(() => {
vi.clearAllMocks()
})Quick Check
Check your understanding of mocking.
Recap
You learned to mock dependencies in tests:
vi.fn()creates tracked functions; control output withmockReturnValue/mockResolvedValue.vi.mock(path, factory)replaces a module (e.g. a composable); it is hoisted above imports.createTestingPiniafrom@pinia/testingprovides a store with stubbed actions you can assert on.vi.spyOn(global, "fetch").mockResolvedValue(...)fakes network calls.- Reset mocks between tests with
vi.clearAllMocks().
Frequently asked questions
Is the “Mocking Composables, Stores, and APIs” lesson free?
Yes — the full text of “Mocking Composables, Stores, and APIs” is free to read here on the web, and the Vue Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Vue Academy course, upgrade to CoddyKit PRO.
What will I learn in “Mocking Composables, Stores, and APIs”?
vi.mock(), mocking useFetch, mocking Pinia stores, mocking router in tests. You practise Vue Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Vue Academy?
No prior experience is required. Vue Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Mocking Composables, Stores, and APIs” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Vue Academy lesson?
Yes. Every Vue Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Vitest Setup for Vue Projects
- Mounting and Querying Components
- Testing User Interactions and Events
- Mocking Composables, Stores, and APIs