You're writing a unit test for a function that sends a welcome email after a user signs up. The function calls emailService.send(). Do you mock email service, or do you let it send a real email during test?
If you mock it, test runs in 2 milliseconds, never touches a mail server, and you can verify that send() was called with right arguments. If you use real email service, test takes 3 seconds, requires a running mail server (or a sandbox like Mailtrap), and sends an actual email to a test inbox that you then have to check.
The answer seems obvious: mock it. And for this case, it is. But "mock everything" is a habit that leads to a test suite that passes perfectly while real system is broken. The question isn't whether to mock. It's knowing which dependencies to mock and which ones to leave real.
The same function, tested both ways
Here's a simple order total calculator that applies a discount from an external pricing service:
class OrderCalculator:
def __init__(self, pricing_service):
self.pricing_service = pricing_service
def calculate_total(self, items, coupon_code):
subtotal = sum(item.price * item.quantity for item in items)
discount = self.pricing_service.get_discount(coupon_code)
return subtotal * (1 - discount)β
Test with a mock:
def test_calculate_total_with_mock():
mock_pricing = Mock()
mock_pricing.get_discount.return_value = 0.10 # 10% off
calculator = OrderCalculator(mock_pricing)
items = [Item(price=100, quantity=2)] # subtotal = 200
total = calculator.calculate_total(items, "SAVE10")
assert total == 180.0
mock_pricing.get_discount.assert_called_once_with("SAVE10")β
The mock replaces pricing service entirely. You tell it "when someone calls get_discount with any argument, return 0.10." The test verifies two things: that math is right (200 * 0.90 = 180) and that function actually called get_discount with coupon code.
This test runs instantly. It doesn't need a pricing service running. It doesn't need a database of valid coupon codes. It tests calculator's logic in isolation.
Test with real pricing service:
def test_calculate_total_with_real_service():
real_pricing = PricingService(db_connection=test_db)
calculator = OrderCalculator(real_pricing)
items = [Item(price=100, quantity=2)]
total = calculator.calculate_total(items, "SAVE10")
assert total == 180.0β
This test uses actual pricing service connected to a test database. The "SAVE10" coupon must exist in that database with a 10% discount value. The test verifies same math, but it also verifies that pricing service can actually look up coupon codes and return right discount.
This test is slower (database query), requires setup (test database with seed data), and can fail for reasons unrelated to calculator (database connection issues, missing seed data). But it catches something mock can't: if pricing service changes its return format from a decimal (0.10) to a percentage (10), mocked test still passes (you hardcoded 0.10), while real test fails and reveals incompatibility.
When to mock
Mock a dependency when it has any of these characteristics:
It's slow. Database queries, HTTP calls to external APIs, file system reads. A test suite with 500 tests that each make a real database call takes minutes. The same suite with mocks takes seconds. Speed matters because slow tests don't get run often, and tests that don't run often don't catch bugs.
It's non-deterministic. The current time, random number generators, external services that return different results on each call. A test that passes on Monday and fails on Tuesday because API returned different data isn't testing your code. It's testing API's mood.
It has side effects. Sending emails, charging credit cards, posting to Slack, writing to a production database. You don't want your test suite sending 500 emails every time it runs. Mock side effect. Verify call was made with right arguments. Don't actually fire missile.
It doesn't exist yet. You're building order calculator before pricing service is ready. Mock interface, write your tests, and swap in real service when it ships. This is where mocking and TDD (test-driven development) work hand in hand.
When to use real objects
Use real thing when dependency has these characteristics:
It's fast and deterministic. Value objects, data classes, in-memory collections, utility functions, math libraries. If creating a real Item(price=100, quantity=2) is as fast as creating a mock of it, use real object. Mocking it adds complexity without benefit.
The behavior IS what you're testing. If you're testing whether your code correctly interacts with a database, mocking database defeats purpose. Use a real (test) database. This is integration testing, and it's supposed to test integration. Mocking both sides of an integration test means you're testing nothing.
The mock would be more complex than real thing. Some objects have simple, stable behavior. An in-memory cache with get() and set() methods takes 3 lines to implement for real. A mock of same cache takes 5 lines of mock setup. Use real thing.
The test doubles taxonomy (without academic jargon)
"Mock" gets used as a catch-all term, but there are actually several types of test doubles. The distinctions matter because they determine what your test actually verifies.
Stub. Returns a hardcoded value when called. Doesn't track whether it was called or how many times. The simplest fake. Use when you just need dependency to return something so code under test can proceed.
Mock. Returns a value AND records that it was called. You can assert afterward: "Was send() called exactly once with argument 'user@test.com'?" Use when interaction itself is what you're testing (you care THAT email was sent, not just that total was calculated).
Spy. Wraps real object. Calls go through to real implementation, but spy also records them. You get real behavior AND interaction tracking. Use when you want real behavior but also need to verify a specific call happened.
Fake. A working implementation that takes shortcuts. An in-memory database instead of PostgreSQL. A local file instead of S3. Fakes actually execute logic. Use when you need realistic behavior without infrastructure overhead.
In practice, most developers use "mock" to mean any of these. Mockito (Java), Jest (JavaScript), and unittest.mock (Python) all create objects that can behave as stubs, mocks, or spies depending on how you configure them.
The mistakes that make mocked tests dangerous
Mocking what you own
You write a UserRepository class. Then you write tests for UserService and mock UserRepository. Now test for UserService doesn't test actual interaction between service and repository. If you rename a method in UserRepository but forget to update UserService, mocked test still passes because mock doesn't know method was renamed. The real code throws a runtime error.
The fix: only mock dependencies you don't control (third-party APIs, external services). For internal dependencies, use real object or write a fake.
Testing mock behavior instead of real behavior
# This test is worthless
def test_get_discount():
mock_pricing = Mock()
mock_pricing.get_discount.return_value = 0.10
result = mock_pricing.get_discount("SAVE10")
assert result == 0.10 # You told it to return 0.10. Of course it's 0.10.β
This test verifies that mock framework works. It doesn't test any of your code. Every mock assertion should test YOUR code's behavior in response to mock's return value, not return value itself.
Never testing with real objects
A team mocks every dependency in every unit test. All 800 tests pass. In production, payment service returns a nested JSON object where test mocks returned a flat one. The app crashes on every payment. The mock encoded an assumption about response format that was never validated against real service.
The fix: complement mocked unit tests with integration tests that use real dependencies. The mocked tests run fast on every commit. The integration tests run nightly (or before each release) and verify that assumptions encoded in mocks still hold.
Over-specifying interactions
# Brittle: tests implementation, not behavior
mock_db.query.assert_called_once_with(
"SELECT * FROM users WHERE id = ?", (42,)
)β
This test breaks if a developer rewrites query to use a JOIN or changes column selection. The user still gets right data. The behavior is correct. But test fails because it was testing exact SQL string, not outcome. Assert on results, not on implementation details.
The practical rule
Mock at boundaries of your system. External APIs, email services, payment gateways, third-party SDKs. These are slow, non-deterministic, or have side effects. They're right things to mock.
Don't mock your own code. If ServiceA calls ServiceB and you own both, test them together. If ServiceB is slow because it calls a database, mock database, not ServiceB.
And always have some tests that use real objects. Even if they're slower. Even if they need a test database. Because mock encodes your assumptions. The real object tests whether those assumptions are true.
FAQ
What is mocking in unit testing?
Mocking replaces a real dependency (database, API, email service) with a fake object that returns predefined values. This isolates code under test, makes tests faster, and avoids side effects like sending real emails or charging real credit cards.
When should I use a mock vs a real object?
Mock when dependency is slow (database, HTTP), non-deterministic (time, random), has side effects (email, payment), or doesn't exist yet. Use real objects when dependency is fast, deterministic, and simple (value objects, in-memory collections, utility functions).
What's difference between a mock, a stub, and a fake?
A stub returns hardcoded values without tracking calls. A mock returns values and records interactions (you can assert "was send() called?"). A fake is a working implementation that takes shortcuts (in-memory database instead of PostgreSQL).
Can too much mocking be bad?
Yes. Over-mocking creates tests that pass while real system is broken. Mocks encode assumptions about how dependencies behave. If those assumptions are wrong, tests give false confidence. Balance mocked unit tests with integration tests that use real dependencies.
What are best mocking frameworks?
Mockito (Java/Kotlin), Jest (JavaScript/TypeScript, built-in mocking), unittest.mock (Python, standard library), Moq (C#), and RSpec Mocks (Ruby). Each follows same core pattern: create a mock, define behavior, run code, assert on interactions.
Should I mock database in unit tests?
Usually yes, for pure unit tests. Use an in-memory database (SQLite) or a repository mock. But also write integration tests that run against a real (test) database to verify queries, migrations, and data integrity. The unit tests run fast on every commit. The integration tests run before each release.
β


