DEV Community

Axmin Shrestha
Axmin Shrestha

Posted on

Mock vs. SpyOn in Vitest with TypeScript: A Guide for Unit and Integration Tests

When to mock dependencies and when to spy on behavior for effective testing.

Testing is the backbone of reliable software, but choosing the right tools for the job can be tricky. In Vitest with TypeScript, two powerful utilities, mock and spyOn, often cause confusion. Let’s break down their use cases, differences, and best practices to help you write better unit and integration tests.


What’s the Difference?

At a glance:

  • mock replaces a function/module’s implementation entirely.
  • spyOn observes a function’s behavior without changing it (unless you tell it to).

When to Use mock

Use vi.mock to isolate your code from external dependencies by replacing their implementations.

Use Cases

  1. Unit Testing Mock dependencies (e.g., APIs, databases) to test a single component in isolation.
   // Mock an API client
   vi.mock('@/services/api', () => ({
     fetchData: vi.fn().mockResolvedValue({ data: 'fake' }),
   }));

   test('displays mocked data', async () => {
     render(<DataFetcher />);
     await waitFor(() => expect(screen.getByText('fake')).toBeVisible());
   });
Enter fullscreen mode Exit fullscreen mode
  1. Avoid Side Effects

    Prevent network calls, file system operations, or other costly actions.

  2. Simulate Edge Cases

    Force errors or unusual responses (e.g., 500 status codes).


When to Use spyOn

Use vi.spyOn to track function calls and arguments while preserving the original behavior.

Use Cases

  1. Integration Testing Verify interactions between modules without breaking their real behavior.
   import * as analytics from '@/utils/analytics';

   test('logs an event on button click', () => {
     const trackSpy = vi.spyOn(analytics, 'trackEvent');
     render(<SignupButton />);
     userEvent.click(screen.getByText('Sign Up'));
     expect(trackSpy).toHaveBeenCalledWith('signup_clicked');
   });
Enter fullscreen mode Exit fullscreen mode
  1. Assert Function Behavior

    Check if a function was called, how many times, or with specific arguments.

  2. Temporary Overrides

    Mock a function for a single test, then restore it:

   const spy = vi.spyOn(validator, 'isValid').mockReturnValue(false);
   // Test invalid case
   spy.mockRestore(); // Restore original
Enter fullscreen mode Exit fullscreen mode

Unit Tests vs. Integration Tests

Test Type Mock (vi.mock) Spy (vi.spyOn)
Unit Tests Mock all dependencies to isolate code. Rarely used. Check indirect side effects.
Integration Mock external services (e.g., APIs). Spy on internal logic (e.g., logging).

Key Differences

Feature mock spyOn
Implementation Replaces the original function. Retains original behavior by default.
Scope Applies to entire modules or functions. Works on individual object methods.
Cleanup Auto-reset with Vitest config. Manually restore with mockRestore().

Combining Mocks and Spies

In complex scenarios, mix both tools:

// Mock an external API
vi.mock('@/services/payment', () => ({
  processPayment: vi.fn().mockResolvedValue({ success: true }),
}));

// Spy on an internal validator
const validateSpy = vi.spyOn(validator, 'validateCard');

test('processes payment with valid card', async () => {
  await submitPayment({ card: '4111 1111 1111 1111' });
  expect(validateSpy).toHaveReturnedWith(true);
  expect(processPayment).toHaveBeenCalled();
});
Enter fullscreen mode Exit fullscreen mode

TypeScript Pro Tips

  1. Type-Safe Mocks Use MockedFunction or MockedObject for better type inference:
   import type { fetchData } from '@/services/api';
   vi.mock('@/services/api');
   const mockedFetch = vi.mocked(fetchData); // Type-safe mock!
Enter fullscreen mode Exit fullscreen mode
  1. Spy Assertions Add type assertions if needed:
   const spy = vi.spyOn(console, 'log') as MockedFunction<typeof console.log>;
Enter fullscreen mode Exit fullscreen mode

Final Advice

  • Unit Tests: Mock aggressively to isolate components.
  • Integration Tests: Spy to validate interactions, mock only externals.
  • Always Restore Spies: Use afterEach(() => vi.restoreAllMocks()) to avoid test pollution.

By mastering mock and spyOn, you’ll write tests that are faster, more reliable, and easier to debug. Happy testing! 🚀


Got questions? Drop them in the comments below!

Follow for more Vitest and TypeScript tips!

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay