Back to Blog
May 13, 2025

Advanced Mocking Strategies and Best Practices

Beyond Basic Mocking

By now, you've learned the fundamentals of testing in Next.js applications. But real-world applications often present complex scenarios that require advanced mocking strategies. Let's explore sophisticated mocking techniques that will help you handle edge cases, complex dependencies, and maintainable test suites.

Advanced Mock Patterns

1. Conditional Mocking

Sometimes you need different mock behavior based on test conditions:

// ✅ DO: Use conditional mocking for different scenarios
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from '@/components/UserProfile';

describe('UserProfile', () => {
  beforeEach(() => {
    global.fetch = jest.fn();
  });

  it('should handle different user roles', async () => {
    const mockUsers = {
      admin: {
        id: 1,
        name: 'Admin User',
        role: 'admin',
        permissions: ['read', 'write', 'delete']
      },
      user: {
        id: 2,
        name: 'Regular User',
        role: 'user',
        permissions: ['read']
      }
    };

    // Test admin user
    (global.fetch as jest.Mock).mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockUsers.admin)
    });

    const { rerender } = render(<UserProfile userId={1} />);
    
    await waitFor(() => {
      expect(screen.getByText('Delete User')).toBeInTheDocument();
      expect(screen.getByText('Edit Profile')).toBeInTheDocument();
    });

    // Test regular user
    (global.fetch as jest.Mock).mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockUsers.user)
    });

    rerender(<UserProfile userId={2} />);
    
    await waitFor(() => {
      expect(screen.queryByText('Delete User')).not.toBeInTheDocument();
      expect(screen.getByText('View Profile')).toBeInTheDocument();
    });
  });
});

2. Dynamic Mock Responses

Create mocks that respond differently based on input parameters:

// ✅ DO: Create dynamic mock responses
import EmailService from '@/services/EmailService';

describe('EmailService', () => {
  let emailService: EmailService;
  let mockTransporter: jest.Mocked<Transporter>;

  beforeEach(() => {
    mockTransporter = {
      sendMail: jest.fn(),
      verify: jest.fn()
    } as any;
    
    emailService = new EmailService(mockTransporter);
  });

  it('should handle different email types', async () => {
    // Create dynamic mock that responds based on email type
    mockTransporter.sendMail.mockImplementation((options) => {
      const { to, subject } = options;
      
      if (subject.includes('Welcome')) {
        return Promise.resolve({ messageId: 'welcome-123' });
      } else if (subject.includes('Password Reset')) {
        return Promise.resolve({ messageId: 'reset-456' });
      } else {
        return Promise.reject(new Error('Unknown email type'));
      }
    });

    // Test welcome email
    const welcomeResult = await emailService.sendWelcomeEmail({
      to: 'user@example.com',
      name: 'John Doe'
    });
    expect(welcomeResult.messageId).toBe('welcome-123');

    // Test password reset email
    const resetResult = await emailService.sendPasswordResetEmail({
      to: 'user@example.com',
      resetToken: 'token123'
    });
    expect(resetResult.messageId).toBe('reset-456');
  });
});

3. Mock Factory Functions

Create reusable mock factories for complex objects:

// ✅ DO: Use mock factories for complex objects
const createMockUser = (overrides: Partial<User> = {}): User => ({
  id: 1,
  name: 'John Doe',
  email: 'john@example.com',
  role: 'user',
  createdAt: new Date('2024-01-01'),
  isActive: true,
  ...overrides
});

const createMockPost = (overrides: Partial<Post> = {}): Post => ({
  id: 1,
  title: 'Test Post',
  content: 'Test content',
  authorId: 1,
  publishedAt: new Date('2024-01-01'),
  tags: ['test'],
  ...overrides
});

describe('UserService', () => {
  it('should handle different user scenarios', async () => {
    const adminUser = createMockUser({ role: 'admin', permissions: ['all'] });
    const inactiveUser = createMockUser({ isActive: false });
    const newUser = createMockUser({ createdAt: new Date() });

    // Test with different user types
    expect(adminUser.role).toBe('admin');
    expect(inactiveUser.isActive).toBe(false);
    expect(newUser.createdAt).toEqual(new Date());
  });
});

Complex Dependency Mocking

1. Mocking Circular Dependencies

Handle services that depend on each other:

// ✅ DO: Mock circular dependencies carefully
import UserService from '@/services/UserService';
import PostService from '@/services/PostService';

// Mock both services to break circular dependency
jest.mock('@/services/UserService');
jest.mock('@/services/PostService');

describe('Service Integration', () => {
  let userService: jest.Mocked<UserService>;
  let postService: jest.Mocked<PostService>;

  beforeEach(() => {
    jest.clearAllMocks();
    
    // Get mocked instances
    userService = new UserService() as jest.Mocked<UserService>;
    postService = new PostService() as jest.Mocked<PostService>;
    
    // Setup cross-service mocks
    userService.getUserById.mockResolvedValue({
      id: 1,
      name: 'John Doe',
      posts: []
    });
    
    postService.getPostsByUserId.mockResolvedValue([
      { id: 1, title: 'Test Post', authorId: 1 }
    ]);
  });

  it('should handle service interactions', async () => {
    const user = await userService.getUserById(1);
    const posts = await postService.getPostsByUserId(user.id);
    
    expect(user.name).toBe('John Doe');
    expect(posts).toHaveLength(1);
    expect(posts[0].authorId).toBe(user.id);
  });
});

2. Mocking Event Emitters

Test components that use event emitters:

// ✅ DO: Mock event emitters properly
import { render, screen, fireEvent } from '@testing-library/react';
import EventComponent from '@/components/EventComponent';

describe('EventComponent', () => {
  let mockEventEmitter: jest.Mocked<EventEmitter>;

  beforeEach(() => {
    mockEventEmitter = {
      on: jest.fn(),
      off: jest.fn(),
      emit: jest.fn(),
      once: jest.fn()
    } as any;
    
    // Mock the event emitter module
    jest.mock('@/lib/eventEmitter', () => ({
      default: mockEventEmitter
    }));
  });

  it('should listen to events', () => {
    render(<EventComponent />);
    
    expect(mockEventEmitter.on).toHaveBeenCalledWith('userUpdate', expect.any(Function));
  });

  it('should emit events on user interaction', () => {
    render(<EventComponent />);
    
    fireEvent.click(screen.getByRole('button', { name: /update/i }));
    
    expect(mockEventEmitter.emit).toHaveBeenCalledWith('userAction', {
      type: 'update',
      timestamp: expect.any(Date)
    });
  });
});

Advanced API Mocking

1. Mocking GraphQL Queries

Test components that use GraphQL:

// ✅ DO: Mock GraphQL queries and mutations
import { render, screen, waitFor } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { gql } from '@apollo/client';
import UserList from '@/components/UserList';

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

const mocks = [
  {
    request: {
      query: GET_USERS
    },
    result: {
      data: {
        users: [
          { id: 1, name: 'John Doe', email: 'john@example.com' },
          { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
        ]
      }
    }
  }
];

describe('UserList', () => {
  it('should render users from GraphQL', async () => {
    render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <UserList />
      </MockedProvider>
    );
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
  });
});

2. Mocking WebSocket Connections

Test real-time components:

// ✅ DO: Mock WebSocket connections
import { render, screen, waitFor } from '@testing-library/react';
import ChatComponent from '@/components/ChatComponent';

describe('ChatComponent', () => {
  let mockWebSocket: jest.Mocked<WebSocket>;

  beforeEach(() => {
    // Mock WebSocket
    mockWebSocket = {
      send: jest.fn(),
      close: jest.fn(),
      addEventListener: jest.fn(),
      removeEventListener: jest.fn(),
      readyState: WebSocket.OPEN
    } as any;
    
    global.WebSocket = jest.fn(() => mockWebSocket) as any;
  });

  it('should connect to WebSocket', () => {
    render(<ChatComponent />);
    
    expect(global.WebSocket).toHaveBeenCalledWith('ws://localhost:3001/chat');
    expect(mockWebSocket.addEventListener).toHaveBeenCalledWith('message', expect.any(Function));
  });

  it('should handle incoming messages', async () => {
    render(<ChatComponent />);
    
    // Simulate incoming message
    const messageEvent = new MessageEvent('message', {
      data: JSON.stringify({ type: 'chat', message: 'Hello!' })
    });
    
    // Get the message handler
    const messageHandler = mockWebSocket.addEventListener.mock.calls.find(
      call => call[0] === 'message'
    )?.[1];
    
    if (messageHandler) {
      messageHandler(messageEvent);
    }
    
    await waitFor(() => {
      expect(screen.getByText('Hello!')).toBeInTheDocument();
    });
  });
});

Time and Date Mocking

1. Mocking Date and Time

Test time-dependent functionality:

// ✅ DO: Mock dates for predictable tests
import { render, screen } from '@testing-library/react';
import TimeComponent from '@/components/TimeComponent';

describe('TimeComponent', () => {
  beforeEach(() => {
    // Mock Date to return a fixed timestamp
    jest.useFakeTimers();
    jest.setSystemTime(new Date('2024-01-01T12:00:00Z'));
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  it('should display current time', () => {
    render(<TimeComponent />);
    
    expect(screen.getByText(/12:00:00/)).toBeInTheDocument();
  });

  it('should update time every second', () => {
    render(<TimeComponent />);
    
    // Advance time by 1 second
    jest.advanceTimersByTime(1000);
    
    expect(screen.getByText(/12:00:01/)).toBeInTheDocument();
  });
});

2. Mocking setTimeout and setInterval

Test components with timers:

// ✅ DO: Mock timers for predictable testing
import { render, screen, waitFor } from '@testing-library/react';
import TimerComponent from '@/components/TimerComponent';

describe('TimerComponent', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  it('should show countdown', () => {
    render(<TimerComponent duration={5} />);
    
    expect(screen.getByText('5')).toBeInTheDocument();
    
    // Advance timer
    jest.advanceTimersByTime(1000);
    expect(screen.getByText('4')).toBeInTheDocument();
  });

  it('should call onComplete when timer finishes', () => {
    const onComplete = jest.fn();
    render(<TimerComponent duration={3} onComplete={onComplete} />);
    
    // Advance timer to completion
    jest.advanceTimersByTime(3000);
    
    expect(onComplete).toHaveBeenCalled();
  });
});

File System Mocking

1. Mocking File Operations

Test file upload and processing:

// ✅ DO: Mock file system operations
import { render, screen, fireEvent } from '@testing-library/react';
import FileUpload from '@/components/FileUpload';

describe('FileUpload', () => {
  beforeEach(() => {
    // Mock File API
    global.File = jest.fn().mockImplementation((content, filename) => ({
      name: filename,
      size: content.length,
      type: 'text/plain',
      text: () => Promise.resolve(content)
    })) as any;
    
    // Mock FileReader
    global.FileReader = jest.fn().mockImplementation(() => ({
      readAsText: jest.fn(),
      result: 'file content',
      onload: null
    })) as any;
  });

  it('should handle file upload', async () => {
    render(<FileUpload />);
    
    const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
    const input = screen.getByLabelText(/upload file/i);
    
    fireEvent.change(input, { target: { files: [file] } });
    
    await waitFor(() => {
      expect(screen.getByText('test.txt uploaded')).toBeInTheDocument();
    });
  });
});

Database Mocking Strategies

1. Mocking Database Transactions

Test database operations:

// ✅ DO: Mock database transactions
import UserService from '@/services/UserService';

describe('UserService', () => {
  let mockDb: jest.Mocked<Database>;
  let userService: UserService;

  beforeEach(() => {
    mockDb = {
      beginTransaction: jest.fn(),
      commit: jest.fn(),
      rollback: jest.fn(),
      query: jest.fn()
    } as any;
    
    userService = new UserService(mockDb);
  });

  it('should handle transaction success', async () => {
    mockDb.beginTransaction.mockResolvedValue();
    mockDb.query.mockResolvedValue([{ id: 1 }]);
    mockDb.commit.mockResolvedValue();

    await userService.createUserWithProfile({
      name: 'John Doe',
      email: 'john@example.com',
      profile: { bio: 'Test bio' }
    });
    
    expect(mockDb.beginTransaction).toHaveBeenCalled();
    expect(mockDb.commit).toHaveBeenCalled();
    expect(mockDb.rollback).not.toHaveBeenCalled();
  });

  it('should rollback on error', async () => {
    mockDb.beginTransaction.mockResolvedValue();
    mockDb.query.mockRejectedValue(new Error('Database error'));
    mockDb.rollback.mockResolvedValue();

    await expect(
      userService.createUserWithProfile({
        name: 'John Doe',
        email: 'john@example.com',
        profile: { bio: 'Test bio' }
      })
    ).rejects.toThrow('Database error');
    
    expect(mockDb.rollback).toHaveBeenCalled();
    expect(mockDb.commit).not.toHaveBeenCalled();
  });
});

Performance Testing with Mocks

1. Mocking Performance APIs

Test performance-sensitive code:

// ✅ DO: Mock performance APIs
import { render, screen } from '@testing-library/react';
import PerformanceComponent from '@/components/PerformanceComponent';

describe('PerformanceComponent', () => {
  beforeEach(() => {
    // Mock performance.now()
    jest.spyOn(performance, 'now').mockReturnValue(1000);
    
    // Mock requestAnimationFrame
    global.requestAnimationFrame = jest.fn(cb => {
      setTimeout(cb, 0);
      return 1;
    });
  });

  it('should measure render time', () => {
    render(<PerformanceComponent />);
    
    expect(screen.getByText(/render time: 0ms/)).toBeInTheDocument();
  });
});

Best Practices for Complex Mocking

1. Mock Organization

// ✅ DO: Organize mocks in a structured way
// __mocks__/services/__mocks__.ts
export const createMockUserService = () => ({
  getUserById: jest.fn(),
  createUser: jest.fn(),
  updateUser: jest.fn(),
  deleteUser: jest.fn()
});

export const createMockPostService = () => ({
  getPosts: jest.fn(),
  createPost: jest.fn(),
  updatePost: jest.fn(),
  deletePost: jest.fn()
});

// In your test file
import { createMockUserService, createMockPostService } from '@/__mocks__/services';

describe('ComplexComponent', () => {
  let mockUserService: ReturnType<typeof createMockUserService>;
  let mockPostService: ReturnType<typeof createMockPostService>;

  beforeEach(() => {
    mockUserService = createMockUserService();
    mockPostService = createMockPostService();
  });
});

2. Mock Cleanup

// ✅ DO: Clean up mocks properly
describe('ComplexTest', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    jest.resetModules();
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  afterAll(() => {
    jest.resetAllMocks();
  });
});

3. Mock Validation

// ✅ DO: Validate mock calls
it('should call service with correct parameters', async () => {
  const mockService = createMockService();
  
  await component.doSomething();
  
  expect(mockService.method).toHaveBeenCalledWith(
    expect.objectContaining({
      id: expect.any(Number),
      name: expect.any(String)
    })
  );
  
  // Verify call count
  expect(mockService.method).toHaveBeenCalledTimes(1);
});

Common Advanced Mocking Pitfalls

❌ DON'T: Over-Mock Complex Systems

// ❌ WRONG: Mocking everything in a complex system
jest.mock('@/services/UserService');
jest.mock('@/services/PostService');
jest.mock('@/services/CommentService');
jest.mock('@/services/NotificationService');
jest.mock('@/services/AnalyticsService');

// This creates a system that does nothing real

❌ DON'T: Ignore Mock Side Effects

// ❌ WRONG: Not considering mock side effects
mockService.method.mockImplementation(() => {
  // This might have side effects that affect other tests
  global.someState = 'modified';
  return 'result';
});

❌ DON'T: Mock What You're Testing

// ❌ WRONG: Mocking the component you're testing
jest.mock('@/components/UserProfile', () => ({
  default: jest.fn().mockReturnValue(<div>Mocked UserProfile</div>)
}));

// This tests the mock, not the real component

The Journey Continues

You've now mastered advanced mocking strategies for Next.js applications. These techniques will help you handle complex testing scenarios and maintain reliable test suites.

Advanced Mocking Checklist

For Complex Dependencies:

  • [ ] Use mock factories for reusable mocks
  • [ ] Handle circular dependencies carefully
  • [ ] Mock at the right abstraction level
  • [ ] Validate mock calls and parameters

For Time-Dependent Code:

  • [ ] Mock dates and timers consistently
  • [ ] Use fake timers for predictable tests
  • [ ] Clean up timer mocks after tests
  • [ ] Test time-based behavior thoroughly

For External Systems:

  • [ ] Mock APIs and databases appropriately
  • [ ] Test error conditions and edge cases
  • [ ] Verify transaction handling
  • [ ] Mock performance APIs when needed

For Maintainability:

  • [ ] Organize mocks in a structured way
  • [ ] Clean up mocks between tests
  • [ ] Document complex mock setups
  • [ ] Avoid over-mocking

Remember: Advanced mocking should make your tests more reliable, not more complex.


This concludes our unit testing journey in Next.js. You now have the knowledge and patterns to write comprehensive, reliable tests for any Next.js application.

testingmockingadvanced-testingnextjsjestcomplex-dependenciescircular-dependenciesgraphql-testingwebsocket-testingdatabase-mockingtime-mockingfile-system-mockingperformance-testingmock-factoriesconditional-mockingtest-organizationbest-practicesjavascripttypescripttest-patterns