Mocking and Stubbing: A Definitive Guide for 2026
Struggling with slow, brittle tests? Learn the critical differences between mocking and stubbing, and how to use them to build resilient, high-performance software systems.
The $50,000 Testing Mistake: Why Isolation Matters
Imagine this: Your CI/CD pipeline has just ground to a halt. A critical deployment for a FinTech app is delayed because a third-party payment gateway's sandbox environment is down. Your unit tests, which should take seconds, are failing because they are trying to make real network calls to an external dependency.
In the world of professional software development, this isn't just an inconvenience—it is a bottleneck that costs thousands of dollars in engineering hours. At Increments Inc., having built complex platforms for clients like Abwaab and Freeletics over the last 14 years, we have seen how improper test isolation can cripple a product's velocity.
To build scalable, resilient software, you must master the art of Mocking and Stubbing. These aren't just technical buzzwords; they are the fundamental tools that allow you to isolate your code, simulate edge cases, and ensure your test suite remains lightning-fast. In this guide, we will dive deep into when and how to use these techniques, moving beyond the basics into architectural strategies used by elite engineering teams.
Understanding the Taxonomy of Test Doubles
Before we differentiate between a mock and a stub, we need to understand the umbrella term: Test Double. Coined by Gerard Meszaros, a Test Double is any object that stands in for a real object during a test. Think of it like a stunt double in a movie—they look like the lead actor, but they are there to perform a specific, often dangerous or difficult, task.
The Five Types of Test Doubles
- Dummy: Objects that are passed around but never actually used. Usually, they are just used to fill parameter lists.
- Fake: Objects that have working implementations, but usually take some shortcut which makes them not suitable for production (e.g., an in-memory database).
- Stub: Provides canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
- Spy: Stubs that also record some information based on how they were called (e.g., an email service that records how many messages it sent).
- Mock: Objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
At Increments Inc., we emphasize that choosing the right double is critical for maintaining a clean architecture. If you're unsure if your current architecture supports effective testing, we offer a free technical audit worth $5,000 to help you identify bottlenecks and improve your testing ROI.
Stubbing: Controlling the State
Stubbing is the practice of replacing a real component with a version that returns hardcoded data. The primary goal of a stub is to provide the 'indirect inputs' to the system under test (SUT).
When to Use a Stub
You use a stub when your code depends on data from an external source that you cannot easily control, such as:
- A database query.
- An API response from a third-party service.
- The current system time or a random number generator.
- Filesystem reads.
Code Example: Stubbing a Currency Converter
Let’s say we are building a feature for a global E-commerce platform (similar to the work we've done for Malta Discount Card). We need to test a PriceCalculator that depends on a CurrencyExchangeService.
// The Real Service (Difficult to test because rates change)
interface ExchangeService {
getRate(from: string, to: string): Promise<number>;
}
// The System Under Test
class PriceCalculator {
constructor(private exchangeService: ExchangeService) {}
async calculateTotal(price: number, currency: string): Promise<number> {
const rate = await this.exchangeService.getRate('USD', currency);
return price * rate;
}
}
// The Test with a Stub
test('calculates price correctly with a fixed rate', async () => {
// A Stub implementation
const exchangeServiceStub: ExchangeService = {
getRate: async () => 1.2 // Canned response
};
const calculator = new PriceCalculator(exchangeServiceStub);
const result = await calculator.calculateTotal(100, 'EUR');
expect(result).toBe(120);
});
In this example, the stub doesn't care how it's called or how many times it's called. It simply provides the state (the rate of 1.2) needed for the PriceCalculator to do its job.
Mocking: Verifying Behavior
While stubs deal with state, Mocks deal with behavior. A mock is an object that focuses on the interactions between objects. It verifies that the SUT called the dependency correctly.
When to Use a Mock
You use a mock when the primary concern is the 'indirect output'—the side effects your code has on other systems. Common scenarios include:
- Ensuring an email was sent after a user registered.
- Verifying that a logging service was called when an error occurred.
- Checking if a message was published to a queue (like RabbitMQ or Kafka).
Code Example: Mocking a Notification System
Imagine we are building an EdTech platform like Abwaab. When a student completes a lesson, we need to notify their mentor.
interface NotificationService {
sendEmail(to: string, subject: string): void;
}
class LessonManager {
constructor(private notifier: NotificationService) {}
completeLesson(studentId: string, mentorEmail: string) {
// ... logic to mark lesson as complete ...
this.notifier.sendEmail(mentorEmail, 'Lesson Completed!');
}
}
// The Test with a Mock
test('notifies mentor when lesson is complete', () => {
// Creating a mock object using a library like Jest
const mockNotifier = {
sendEmail: jest.fn()
};
const manager = new LessonManager(mockNotifier as any);
manager.completeLesson('student_123', '[email protected]');
// Verification: Did the interaction happen correctly?
expect(mockNotifier.sendEmail).toHaveBeenCalledWith(
'[email protected]',
'Lesson Completed!'
);
});
Here, we aren't testing the return value of sendEmail. We are testing that the LessonManager actually triggered the email with the correct parameters.
Mocks vs. Stubs: The Comparison Matrix
Understanding the nuance between these two can be tricky. Use the following table to guide your decision-making process.
| Feature | Stub | Mock |
|---|---|---|
| Primary Purpose | Provide data to the SUT (Indirect Input) | Verify interactions by the SUT (Indirect Output) |
| Focus | State-based testing | Interaction-based testing |
| Assertion | You assert on the SUT's return value or state | You assert on the Mock's calls and arguments |
| Complexity | Usually simpler; hardcoded values | More complex; requires expectation setup |
| Usage Scenario | Querying a database or API | Sending an SMS, Email, or Webhook |
| Failure Mode | Test fails if the SUT produces wrong output | Test fails if the SUT doesn't call the dependency as expected |
Architectural Implications of Testing
At Increments Inc., we believe that if a piece of code is hard to mock or stub, it is likely a sign of poor architectural design. High coupling makes testing difficult. To make your code "testable," you should follow the Dependency Inversion Principle (DIP).
The Dependency Injection Pattern
Instead of instantiating dependencies inside your class, pass them through the constructor. This allows you to swap real implementations for mocks or stubs during testing without changing a single line of production logic.
Bad Architecture (Hard to Test):
class OrderService {
process() {
const db = new Database(); // Hard dependency
db.save(this.order);
}
}
Good Architecture (Easy to Test):
class OrderService {
constructor(private db: DatabaseInterface) {} // Injected dependency
process() {
this.db.save(this.order);
}
}
ASCII Diagram: The Mocking Workflow
+---------------------+ +--------------------------+
| Unit Test | | System Under Test (SUT)|
+----------+----------+ +------------+-------------+
| |
| 1. Setup Mock Expectations |
+---------------------------------->|
| |
| 2. Execute Action |
+---------------------------------->|
| | 3. Call Dependency
| +-----------> +-----------------+
| | | Mock / Stub |
| |<----------- +-----------------+
| | 4. Return Canned Data
| 5. Verify Interactions |
+---------------------------------->|
| |
+----------v----------+ +------------v-------------+
| Pass / Fail | | End Execution |
+---------------------+ +--------------------------+
Advanced Strategies: When NOT to Mock
While mocking is powerful, there is a dangerous anti-pattern known as "Mocking the World." If your tests consist entirely of mocks, you are no longer testing the behavior of your system; you are simply testing that your code calls other code. This leads to "brittle tests" that break every time you refactor, even if the end result is the same.
1. Don't Mock What You Don't Own
One of the golden rules of software engineering is: Only mock your own abstractions. If you mock a third-party library (like a specific AWS SDK or a database driver), and that library updates its API, your tests will still pass, but your production code will crash.
The Solution: Create a wrapper (Adapter pattern) around the third-party tool and mock your wrapper instead.
2. Avoid Mocking Data Objects
If you have a simple Data Transfer Object (DTO) or a Value Object, don't mock it. Just use a real instance. Mocking simple objects adds unnecessary complexity and overhead.
3. The Integration Test Balance
Mocks and stubs are for Unit Tests. However, you still need Integration Tests where real components interact. At Increments Inc., we typically aim for the testing pyramid: 70% Unit Tests (heavy mocking/stubbing), 20% Integration Tests (minimal mocking), and 10% E2E Tests (no mocking).
Are you struggling with a test suite that is either too slow or too brittle? Our team at Increments Inc. specializes in modernizing legacy platforms and optimizing DevOps pipelines. Start a project with us today and get a comprehensive technical audit of your existing codebase.
Real-World Case Study: SokkerPro
When we worked on SokkerPro, a high-traffic sports analytics platform, we faced a challenge: the system relied on real-time data feeds from multiple sports providers. Testing the prediction algorithms with live data was impossible due to the volatility of the feeds.
The Strategy:
- Stubbing: We created stubs for the data feeds, allowing us to inject specific match scenarios (e.g., a last-minute goal, a red card) to see how the algorithms reacted.
- Mocking: We used mocks to verify that when a high-probability betting signal was generated, the system correctly triggered push notifications and updated the user dashboard via WebSockets.
This approach allowed us to achieve 90%+ code coverage on the core engine without ever needing to wait for a real Saturday afternoon football match to run our CI suite.
Implementing Mocks and Stubs in Different Environments
While the concepts are universal, the implementation varies by language. Here is a quick overview of the most popular tools in 2026:
JavaScript / TypeScript
- Jest: The industry standard with built-in
jest.fn()andjest.spyOn(). - Vitest: A blazing-fast alternative for Vite-based projects.
- Sinon.js: Great for standalone mocking and stubbing if you aren't using a full test runner.
Python
- unittest.mock: The built-in library that provides the powerful
MockandMagicMockclasses. - pytest-mock: A wrapper that makes using mocks within the pytest framework much cleaner.
Go (Golang)
- GoMock: A mocking framework that integrates well with
go test. - Testify: Includes a very popular
mockpackage that uses interfaces to generate doubles.
How Increments Inc. Can Help You Scale
Building software is more than just writing code; it's about building a system that can evolve. At Increments Inc., we don't just deliver features; we deliver engineered excellence.
When you partner with us for custom software development or platform modernization, you get more than just a vendor. You get a partner with 14+ years of experience in Dhaka and Dubai, serving global clients.
Our Unique Offer:
Every project inquiry at Increments Inc. receives:
- A Free AI-Powered SRS Document: Based on the IEEE 830 standard, we help you define your project requirements with surgical precision before a single line of code is written.
- A $5,000 Technical Audit: We analyze your existing infrastructure, identifies security gaps, and provides a roadmap for scalability—completely free of charge.
Whether you are a startup looking to build an MVP or an enterprise needing AI integration, we ensure your testing strategy is built for 2026 and beyond.
Connect with our engineering experts on WhatsApp or visit our Start a Project page to begin.
Key Takeaways
- Test Doubles are the broad category; Stubs and Mocks are specific types.
- Use Stubs when you need to provide data (indirect input) to your code.
- Use Mocks when you need to verify that your code interacted correctly with a dependency (indirect output).
- Dependency Injection is the secret sauce that makes mocking and stubbing possible.
- Avoid Over-Mocking: Don't mock third-party libraries; wrap them and mock your own interfaces.
- Balance is Key: Use unit tests for speed and isolation, but don't skip integration tests for confidence.
Mastering these concepts will transform your development workflow from a game of "trial and error" into a disciplined engineering process. If you're ready to take your product to the next level with a team that understands these nuances deeply, Increments Inc. is ready to build with you.
Ready to build something incredible?
Start your project with Increments Inc. today
Topics
Written by
Increments Inc.
Engineering Team
Want to build something?
Get a free consultation and technical audit worth $5,000. We'll help you build your next successful product.
- Free $5,000 technical audit
- No upfront payment required
- 14+ years of experience
Explore More Articles
AI-Driven Quality Control in RMG: A Detailed Look
Discover how AI-driven quality control is revolutionizing the RMG sector in 2026, reducing fabric waste by 70% and boosting accuracy to 99.7% through advanced computer vision.
Read ArticleSmart Grid: The Key to a More Efficient Energy System in 2026
Explore how Smart Grid technology is revolutionizing energy efficiency through AI, IoT, and decentralized architectures. Learn why the transition from legacy systems to intelligent infrastructure is critical for the 2026 energy landscape.
Read ArticleTop Digitization Technologies for RMG: A 2026 Review
Explore the cutting-edge technologies transforming the Ready-Made Garment (RMG) sector in 2026, from AI-driven demand forecasting to blockchain-enabled Digital Product Passports.
Read Article