Mocks vs Stubs The Definitive Developer Guide
Here's the simplest way to think about the core difference: Stubs provide canned answers to method calls, letting you check an object's final state. On the other hand, mocks are intelligent objects that verify the behavior and interactions between your code and its dependencies.
Ultimately, your choice boils down to a simple question: Do you need to check a result, or do you need to confirm a process?
Understanding the Core Difference in Testing

When you're trying to build a solid application, you have to test your components in isolation. That's where test doubles like mocks and stubs come into play—they act as stand-ins for real dependencies like databases or external APIs. But they aren't interchangeable. They serve fundamentally different purposes, and knowing which one to use comes from understanding two distinct testing philosophies.
Stubs are the simple, straightforward members of the test double family. They're basically hard-coded implementations that give you fixed, predictable responses. This makes them perfect when all you care about is checking the final outcome of an operation.
Mocks, however, are much more sophisticated. They're designed not just to simulate responses but to actively verify the interaction patterns between your components. It’s less about the result and more about how you got there.
State vs. Behavior Verification
A stub's main job is to help with state verification. Let's say you have a function that calculates the total price of a user's shopping cart. You could use a stub to replace the actual database connection and have it return a fixed list of products with set prices. After your function runs, the test simply asserts that the final calculated price is correct. The stub doesn't care how the calculation happened, only that the resulting state (the total price) is what you expected.
In contrast, a mock is all about behavior verification. It cares about the journey, not just the destination. For example, imagine that adding a new user to your system is supposed to trigger a welcome email. You’d use a mock of your email service in your test. The test itself wouldn't check a return value; instead, it would verify that the sendEmail method on the mock was called exactly once, and with the correct user's email address.
This distinction is crucial because it affects how you write your tests and even how you design your software. These ideas are a key part of the larger testing world; for a broader view, you might check out this comprehensive guide to the Quality Assurance Testing Process. These concepts also tie into bigger strategies like service virtualization, which you can read about here: https://dotmock.com/blog/what-is-service-virtualization.
Mocks vs Stubs At a Glance
To make the differences even clearer, here's a quick side-by-side comparison that gets straight to the point.
| Attribute | Stubs | Mocks |
|---|---|---|
| Primary Goal | State Verification (Checks the final result) | Behavior Verification (Checks the interactions) |
| Complexity | Simple, returns hard-coded values | More complex, programmed with expectations |
| Test Focus | Validates the state of the object under test | Validates interactions with dependencies |
| Failure Condition | Fails if the object's state is incorrect | Fails if interactions don't meet expectations |
This table should give you a solid, high-level understanding of when you might reach for one over the other. Stubs are for outcomes, and mocks are for the process.
When to Use Stubs for State Verification

You should reach for a stub when your test’s primary goal is state verification. This is all about confirming that your object lands in a specific state after a method runs. You’re not concerned with how it got there—just the end result. Think of a stub as a stand-in that simply provides a canned, predictable response.
Their real power comes from isolating the component you're testing from its dependencies. This is a lifesaver when those dependencies are slow, flaky, or a pain to set up. We're talking about things like network calls to an external API or queries to a live database. By swapping out the real dependency with a stub, you make your test fast, reliable, and laser-focused on the logic you actually want to verify.
Practical Application: Simulating Dependencies
Let’s say you’re testing an OrderService that calculates the total cost of a shopping cart. To do this, it needs to fetch product prices from a ProductRepository, which in turn talks to a database. Making a real database call would make your unit test slow and fragile.
This is a textbook case for using a stub. You can quickly create a ProductRepositoryStub that returns a hard-coded price whenever its getPrice method is called. No database, no network, no fuss.
A stub's job is purely functional. It's there to provide just enough data to let the test run its course. It doesn't track method calls or verify interactions; it just returns a predefined value so you can check the final state of your object.
This approach lets your test focus on one simple question: did the OrderService correctly calculate the total? The assertion becomes a straightforward check of the final state—the total cost—based on the controlled input provided by the stub.
Code Example: A Simple Price Stub
Let's see what this looks like in practice. Here's a Python example where we use a stub to test an Order class.
First, we define a stub for our repository that always returns a fixed price. It’s incredibly simple, and that’s the point.
class ProductRepositoryStub:
def get_price(self, product_id):
# Always return a fixed price for any product
return 50.00
Next, in our test, we inject this stub into the Order class and check its final state.
def test_order_total_is_calculated_correctly():
# Arrange: Create the stub and the class under test
stub_repo = ProductRepositoryStub()
order = Order(stub_repo)
# Act: Perform the action
order.add_product("product-123")
# Assert: Verify the final state
assert order.get_total() == 50.00
The beauty of this is its simplicity. The test is fast, easy to read, and completely independent of any external database. But this simplicity does come with a trade-off. If the real ProductRepository's method signature ever changes (for instance, from get_price to fetch_price), the stub won't know about it. Your unit tests will keep passing, potentially hiding a nasty integration bug that you’ll only find later.
How to Use Mocks for Behavior Verification

While stubs are great for verifying the final state of an object, mocks answer a fundamentally different question: "Did my code do the right things along the way?" They act as smart stand-ins, specifically designed to check the interactions between your code and its dependencies.
Think of it as behavior verification. Instead of just returning pre-set data, you program a mock with a set of expectations. This lets you confirm that specific methods were called, check how many times they were invoked, and even inspect the exact arguments they received.
Setting and Verifying Expectations
Let's say you're testing a UserManager class. Its job is to send a welcome email through a NotificationService whenever a new user signs up. In this test, you don't really care about the final state of the UserManager itself. What you need to know is whether it properly told the NotificationService to do its job.
This is a textbook case for a mock. You'd create a mock version of the NotificationService and tell your test to expect its sendWelcomeEmail method to be called exactly once, with the new user's email address as the argument.
Mocks are like vigilant supervisors. They don’t just supply answers; they watch the entire process and report back on whether your code followed the rules. This is absolutely critical for testing side effects—things like sending emails, writing to a log, or hitting an external API.
This approach ensures your components are "talking" to each other correctly. It enforces the agreed-upon contract between different parts of your system, a concept we explore more deeply in our guide on what contract testing is and how it helps build robust applications.
Code Example Verifying Method Calls
Let's look at a practical example using Python's unittest.mock library. Here, we'll verify that our UserManager actually calls the notification service when a user signs up.
from unittest.mock import Mock
Assume UserManager and NotificationService classes exist
class UserManager:
def init(self, notification_service):
self._notification_service = notification_service
def sign_up_user(self, email):
# ... user creation logic ...
self._notification_service.send_welcome_email(email)
def test_user_signup_sends_welcome_email():
# Arrange: Create a mock of the dependency
mock_notification_service = Mock()
user_manager = UserManager(mock_notification_service)
user_email = "[email protected]"
# Act: Perform the action
user_manager.sign_up_user(user_email)
# Assert: Verify the interaction
mock_notification_service.send_welcome_email.assert_called_once_with(user_email)
That final line, assert_called_once_with, is where the magic happens. The test will fail if the method was never called, called too many times, or called with the wrong email.
But this power comes with a significant trade-off. Mocks can couple your tests very tightly to implementation details. If a developer later refactors the UserManager to use a method with a slightly different name (say, sendNewUserEmail), the test will break—even if the underlying functionality is identical and the user still gets their email.
When you're trying to decide between a stub and a mock, you're really asking a more fundamental question about your testing goals. What are you trying to prove? Do you need to confirm the final result of an action, or are you more interested in how your code achieved that result?
That's the heart of the matter. It comes down to two very different testing philosophies: state verification versus behavior verification. If your test just needs to check that an object ends up in the right state after a method call, you’re performing state verification. That’s a job for a stub. If, however, you need to prove that your code interacted with its dependencies in a specific way, you’re performing behavior verification—and that’s where mocks shine.
This isn't just a trivial distinction; it marks a major shift in how developers approach testing. Back in the early 2000s, mocking frameworks completely changed the game by introducing behavior verification. Suddenly, tests could go beyond simple state checks to confirm that specific methods were called in the right order and with the correct arguments. This evolution, highlighted by thought leaders like Martin Fowler, pushed for a new style of testing that often leads to cleaner, more decoupled designs.
This decision tree gives you a quick mental model for making the choice.

As you can see, the decision really pivots on whether you're testing the interactions themselves. That’s the core difference between mocks vs stubs.
Applying The Concepts To A Real-World Scenario
Let's make this concrete with a classic e-commerce checkout. When a customer finalizes a purchase, a few things must happen behind the scenes. Your system needs to charge the payment gateway, update the order’s status in the database, and fire off a confirmation email.
To test this entire workflow, you'd strategically use both stubs and mocks. Each one plays a distinct and crucial role in making sure the whole process is solid.
A great rule of thumb is: use a stub when you need to provide canned data into your test, and use a mock when you need to verify an action that happens as an output or side effect.
Think about the payment gateway. When you’re testing the Order processing logic, you don't really care about the intricate details of the payment service. You just need to simulate its response.
- You'd use a stub for the payment gateway. The stub’s only job is to return a pre-programmed "payment successful" response. This allows your test to focus on one thing: verifying that the
Orderobject’s status correctly transitions toPaid. That's pure state verification.
Now consider the confirmation email. The order processing method doesn't return anything about the email; it just tells a notification service to send it. This is a side effect, an interaction you need to confirm happened.
- You'd use a mock for the notification service. You would set an expectation on this mock, telling it, "I expect the
sendOrderConfirmationmethod to be called exactly once, and it must be called with these specific order details." This is classic behavior verification.
To make this even clearer, let's look at a few more scenarios.
Situational Use Cases for Mocks and Stubs
The table below breaks down common testing situations to help you decide which test double is the right fit for the job.
| Testing Scenario | Best Choice | Rationale |
|---|---|---|
| Verifying a calculation result | Stub | You need to provide fixed input values (e.g., from a database or external API) to ensure the calculation logic is correct. |
| Confirming an email was sent after user registration | Mock | Sending an email is a side effect. You need to verify the send method of an email service was called with the right details. |
| Testing error handling for a failed API call | Stub | You want to see how your code reacts to a failure state. The stub provides a canned "error" response to trigger that path. |
| Ensuring a logging service is called on an exception | Mock | Logging is an interaction, not a state change in your main object. You need to check that the logError method was invoked. |
| Simulating a user's permissions for an action | Stub | The test needs a user object with a specific role ("Admin," for example) to check if your access control logic works. |
| Checking if a cache is cleared on data update | Mock | Clearing a cache is an interaction with a dependency. You need to verify that the cache.invalidate() method was called. |
Ultimately, the choice between a stub and a mock isn't about which one is "better" in a general sense. It's about what you need to prove in your specific test. For verifying tangible results and state changes, stubs are simple and direct. For verifying interactions and side effects, mocks are absolutely essential.
A Practical Framework for Deciding What to Use
Knowing the theory behind mocks and stubs is one thing, but making the right call in the middle of a complex project is another beast entirely. To cut through the noise, you need a simple but effective way to decide.
It all starts with one core question: What am I actually trying to test here? The answer almost always shines a spotlight on the right tool for the job.
Are you trying to see if a function spits out the right number? Or maybe you need to confirm that an object’s status changed correctly after an operation. In these situations, you're testing a value. A stub is exactly what you need. It feeds your code a predictable piece of data, letting you focus entirely on the outcome.
But what if you need to be sure an email was actually sent, a log file was written, or a critical API call was made? Now, you're testing an interaction. That’s mock territory. Mocks are built to act as spies, verifying that these essential side effects happened just as you designed them to.
Guiding Questions for Your Decision
Before you start coding a test, run through these quick questions to sharpen your focus:
- Am I checking a final result or a process along the way? A final result usually points to a stub, while verifying a process screams for a mock.
- Is it critical to confirm a side effect happened? If the answer is yes, you absolutely need a mock to verify that interaction.
- Does my code's logic depend on a specific response from a dependency? A stub is perfect for feeding it a pre-canned success or failure message.
- Am I just trying to isolate my code from an outside service? Both can do this, but if all you need is some data, a stub is far simpler.
The core takeaway is this: use stubs to provide state and drive the flow of your test, and use mocks to verify behaviors that happen as a result of that flow. A common rookie mistake is using mocks for everything, which often leads to brittle tests that shatter the moment you refactor the smallest piece of implementation code.
This isn't just theory; it's backed by how developers work in the real world. A 2023 developer survey revealed that 78% of developers use mock frameworks specifically to verify behaviors. In contrast, only 42% said they use stubs regularly, mainly for setting up state in more straightforward tests. You can find more details on these testing patterns on turing.com, but the data clearly shows these tools play distinct, complementary roles.
Common Scenarios and the Right Choice
As a rule of thumb, stubs are fantastic for testing pure functions and algorithms—anywhere you can control the inputs and just need to check the output.
Mocks, on the other hand, become non-negotiable when you're verifying how your code interacts with the outside world. This is especially true for external services. For instance, if you're working with an authenticated API, you'll need to know how to get an API key, and your tests will almost certainly rely on mocking to simulate those authenticated interactions without making real network calls.
If you're interested in going deeper on that topic, our guide on how to test REST APIs is a great next step. By keeping this simple framework in mind, you’ll start writing tests that are more focused, more robust, and a whole lot easier to maintain.
Common Mistakes and Best Practices
When you're deep in the weeds of testing, it’s easy to fall into a few common traps with mocks and stubs. These missteps can make your test suite brittle and a real pain to maintain.
One of the biggest anti-patterns I see is over-mocking. This is where developers get a bit too enthusiastic and mock every single dependency. The result? Your tests become so tightly coupled to the how of your code that a simple, harmless refactor can trigger a domino effect of failures, even when the functionality is perfectly fine.
Another classic mistake is writing stubs that are way too smart for their own good. Remember, a stub's job is to provide a simple, canned answer. If you find yourself building complex logic into a stub, you've essentially created a new piece of code that needs its own tests. That completely defeats the purpose.
Adopting a Better Strategy
So, how do we avoid these headaches? A few core principles will help keep your tests clean, readable, and genuinely useful, turning your test suite into an asset instead of a burden.
Here’s what I recommend focusing on:
- Test Public APIs, Not Internals: Your tests should be like a customer of your class—they should only care about its public-facing behavior. Testing private methods ties your tests directly to the implementation, making them fragile and resistant to even minor refactoring.
- Keep Your Test Doubles Simple: Whether it's a mock or a stub, it should do the absolute bare minimum required for the test. Simplicity is key. It makes your tests easier to understand at a glance and much easier to fix when they break.
- Mock Only What You Own: Try to avoid mocking types from third-party libraries or frameworks. It's a recipe for disaster when those libraries update. A much better approach is to wrap external dependencies in your own adapter or service and mock that interface instead. This insulates your code from changes you don't control.
The real goal here is to verify observable outcomes. Use mocks to confirm critical, externally visible side effects—think sending an email or charging a credit card. Use stubs to set up the perfect conditions for your test to run smoothly.
By sticking to these guidelines, you can build a testing strategy that truly validates your application's behavior. You'll end up with a robust suite that adds real value, rather than a maintenance nightmare that slows everyone down.
Frequently Asked Questions About Mocks and Stubs
Even after you get the hang of mocks and stubs, a few tricky questions always seem to come up when you're in the middle of writing tests. Let's tackle some of the most common ones I've heard over the years.
Can I Use Mocks and Stubs in the Same Test?
You absolutely can. In fact, it's often the best way to handle complex tests.
Think about testing a service with several dependencies. You might use a stub to feed your code a hardcoded configuration value, which sets up the state. At the same time, you could use a mock to verify that your logging service was called with the exact error message you expected, which checks the behavior.
This mix-and-match approach is incredibly powerful. You get to control the test environment with stubs while using mocks to make sure the right side effects happened. It helps keep your tests focused and precise.
Combining mocks and stubs is a pro move. Use stubs to set the stage with predictable data, and use mocks to confirm your code took the right actions.
Are Fakes and Spies the Same as Mocks or Stubs?
Nope, they're different players on the same team. Mocks and stubs are the most common, but it's good to know the whole lineup of test doubles.
- Fakes: A fake is a lightweight, working implementation of a dependency, but it's not production-ready. The classic example is using an in-memory database for tests instead of connecting to a real one.
- Spies: A spy is like a stub with a notebook. It returns real data just like a stub, but it also records every interaction. This lets you check how it was called after the test runs, without setting up strict expectations beforehand like you would with a mock.
Does Overusing Mocks Lead to Bad Test Design?
It definitely can. Falling into the "mock everything" trap is a common mistake that leads to brittle tests. When your tests are obsessed with the internal implementation details of your code, they become tightly coupled to it.
The result? Even a tiny, harmless refactor can trigger a waterfall of failing tests. It's frustrating and slows everyone down.
A much better approach is to mock only what you have to—typically external dependencies or components that produce important side effects. For everything else, try using real objects or simple stubs. Your tests will be more resilient, easier to maintain, and better at validating what your system actually does.
Ready to build resilient applications with powerful, zero-configuration API mocking? dotMock lets you simulate any scenario, from success cases to network failures, in seconds. Get started for free and ship faster.