Logo

dev-resources.site

for different kinds of informations.

The Case Against Mocking Libraries

Published at
8/21/2024
Categories
softwaretesting
jest
mocking
Author
sharma-tech
Categories
3 categories in total
softwaretesting
open
jest
open
mocking
open
Author
11 person written this
sharma-tech
open
The Case Against Mocking Libraries

In the world of software testing, mocking libraries have long been a popular tool for isolating components and simulating dependencies. However, as our understanding of clean code and maintainable tests evolves, there's a growing sentiment that over-reliance on mocking libraries can lead to brittle, hard-to-maintain test suites. This article explores why you might want to reconsider your use of mocking libraries and opt for custom fakes instead.

The Problem with Mocking Libraries

  1. Ugly Syntax: Many mocking libraries introduce their own DSL (Domain Specific Language) for setting up mocks. This can make tests harder to read and understand, especially for developers who aren't intimately familiar with the mocking framework.

  2. Tight Coupling to Implementation: Mocks often require detailed knowledge of the internal workings of the system under test. This can lead to tests that are tightly coupled to implementation details, making them fragile and prone to breaking when refactoring.

  3. Overuse and Abuse: While mocking libraries can be useful for verifying specific interactions, they're often overused. Developers may find themselves mocking every dependency, leading to tests that are more about the mocks than the actual behaviour being tested.

  4. Inconsistent Assumptions: When mocks are scattered throughout a test suite, each mock may make different assumptions about how a dependency should behave. This can lead to inconsistencies and make it harder to reason about the expected behaviour of the system.

The Case for Custom Fakes

Instead of relying on mocking libraries, consider creating your own fake implementations of dependencies. Here's why:

  1. Cleaner Syntax: Custom fakes use plain language constructs, making them easier to read and understand without knowledge of a specific mocking framework.

  2. Reduced Coupling: Fakes can be designed to mimic the public interface of a dependency without exposing implementation details, reducing coupling between tests and production code.

  3. Consistent Behaviour: By creating a single fake implementation of a dependency, you ensure consistent behaviour across all tests that use that dependency.

  4. Less Code: Tests using fakes often require less setup code compared to those using mocking libraries, leading to more concise and focused tests.

  5. Better Encapsulation: Fakes allow you to encapsulate complex behaviour in a reusable way, which can be especially useful for simulating external services or complex components.

Example: Refactoring from Mocks to Fakes

Let's look at an example of how we can refactor a test from using mocks to using a custom fake:

// Using a mocking library
import { jest } from '@jest/globals';

interface IUser {}
interface IUserStore {
  findById(id: string): IUser | undefined;
  store(user: IUser): void;
}

class User implements IUser {}

class UserService {
  constructor(private userStore: IUserStore) {}

  activate(user: IUser): void {
    this.userStore.store(user);
  }
}

test('UserService should activate user - using mocks', () => {
  // Arrange
  const user = new User();
  const userStoreMock = {
    findById: jest.fn().mockReturnValue(user),
    store: jest.fn(),
  };

  const userService = new UserService(userStoreMock);

  // Act
  userService.activate(user);

  // Assert
  expect(userStoreMock.store).toHaveBeenCalledTimes(1);
  expect(userStoreMock.store).toHaveBeenCalledWith(user);
});

// Using a custom fake
class UserStoreFake implements IUserStore {
  users: IUser[] = [];

  constructor(user?: IUser) {
    if (user) {
      this.users.push(user);
    }
  }

  findById(id: string): IUser | undefined {
    // Simplified for example, would likely use ID in a real implementation
    return this.users[0];
  }

  store(user: IUser): void {
    this.users.push(user);
  }
}

test('UserService should activate user - using a fake', () => {
  // Arrange
  const user = new User();
  const userStoreFake = new UserStoreFake(user);
  const userService = new UserService(userStoreFake);

  // Act
  userService.activate(user);

  // Assert
  const storedUser = userStoreFake.users.slice(-1)[0];
  expect(storedUser).toBeDefined();
});
Enter fullscreen mode Exit fullscreen mode

In the refactored version, we've replaced the mock with a custom UserStoreFake. This fake implementation can be reused across multiple tests, ensuring consistent behaviour and reducing the amount of setup code needed in each test.

Conclusion

While mocking libraries have their place in the testing toolkit, they should be used judiciously. By favouring custom fakes over mocks, we can create cleaner, more maintainable, and more resilient test suites. This approach encourages us to think more deeply about the contracts between components and helps ensure that our tests remain valuable as our codebase evolves.

Remember, the goal of testing is not just to increase code coverage, but to provide confidence in the behaviour of our system. By writing cleaner tests with custom fakes, we can achieve this goal more effectively and with less maintenance overhead.

mocking Article's
30 articles in total
Favicon
4. Testing (async) searchParams with Jest in Next 15
Favicon
Enhancing API Workflows: A Comprehensive Guide to Mock Responses and Efficient Testing
Favicon
Dummy API for Testing: How to Use It vs Mocking
Favicon
How Better Mocking Makes Building APIs Smoother and Speed Up Development
Favicon
A Step-by-Step Guide to API Creation and Mocking for Faster Development
Favicon
How I've been trying to improve mocking with Zod
Favicon
How to Set Up a Mock Server
Favicon
Best API Mocking Platforms in 2024
Favicon
Server Side Mocking for Playwright in NextJS (App Router) using Mock Service Worker
Favicon
How Mocking Against a Public API Endpoints within Blackbird Gets your API in Production Faster
Favicon
10 Best API Mocking Tools (2025 Review)
Favicon
Introduction to Jest: Unit Testing, Mocking, and Asynchronous Code
Favicon
Mocking an AI Chatbot API with Blackbird
Favicon
Incredibly Useful WireMock with IntelliJ IDEA
Favicon
Simplifying Mocking APIs for Faster Development: Basic & Advanced Techniques
Favicon
The Case Against Mocking Libraries
Favicon
Mocking an AI Chatbot API with Blackbird
Favicon
API Mocking: A Comprehensive Guide
Favicon
Mock TypeORM Package
Favicon
Level Up Your Node.js Testing with Native Test Runner and Mocks: A BigQuery Example
Favicon
Unit Tests for Frontend Developers [Part 2]
Favicon
Revamped Mock-API.net: Simplifying API Mocking for Developers
Favicon
Mocking with Sinon.js: A Comprehensive Guide
Favicon
Streamline Development with Effective API Mocking
Favicon
API mocking with Mock Service Worker + Vue.js
Favicon
Partial: how NOT to mock the whole world
Favicon
Introducing Mockallan: Testing with API-Level Stubs and Mocks
Favicon
Como fazer Deploy de uma api com Json-Server
Favicon
What's the real ROI of writing Mocks?
Favicon
Unleashing GraphQL Type-Based Mocking

Featured ones: