Bird
Raised Fist0
PyTesttesting~15 mins

Testing with external services in PyTest - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Testing with external services
What is it?
Testing with external services means checking how your software works when it talks to other systems outside your code, like databases, web APIs, or cloud services. Instead of just testing your code alone, you test how it behaves when it sends or receives data from these outside helpers. This helps catch problems that only happen when your software connects to real-world services. It often involves special techniques to avoid slow or unreliable tests.
Why it matters
Without testing external services, your software might break when it tries to connect to real systems, causing bugs or crashes in production. External services can be slow, change unexpectedly, or be unavailable, so testing helps ensure your software handles these situations gracefully. It also builds confidence that your software works correctly in the real world, not just in isolated code. Without this, users might face errors or bad experiences.
Where it fits
Before this, you should understand basic unit testing and how to write tests in pytest. You should also know about mocking and stubbing, which are ways to fake parts of your code. After learning this, you can explore integration testing, end-to-end testing, and continuous integration setups that run tests automatically.
Mental Model
Core Idea
Testing with external services means simulating or controlling outside systems so your tests can check real interactions without depending on those systems being always available or fast.
Think of it like...
It's like rehearsing a play with stand-in actors instead of the real cast, so you can practice scenes without waiting for everyone to be present or risk mistakes from missing actors.
┌─────────────────────────────┐
│        Your Software        │
│  ┌─────────────────────┐    │
│  │  External Services   │    │
│  │  (APIs, Databases)  │    │
│  └─────────┬───────────┘    │
│            │                │
│  ┌─────────▼───────────┐    │
│  │  Test Environment   │    │
│  │  (Mocks, Stubs,     │    │
│  │   Simulators)       │    │
│  └─────────────────────┘    │
└─────────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding external services
🤔
Concept: What external services are and why they matter in testing.
External services are systems outside your code that your software talks to, like web APIs, databases, or cloud storage. They provide data or functionality your software needs. Testing with them means checking if your software correctly sends requests and handles responses from these services.
Result
You know what external services are and why your software depends on them.
Understanding external services is key because many bugs happen when your software interacts with things outside itself, not just inside.
2
FoundationBasics of pytest testing
🤔
Concept: How to write simple tests using pytest framework.
pytest lets you write functions that check if your code works. You write assert statements to compare expected and actual results. For example, testing a function that adds numbers: def test_add(): assert 1 + 1 == 2 Running pytest runs this test and tells you if it passes or fails.
Result
You can write and run basic tests using pytest.
Knowing pytest basics is essential because it's the tool you'll use to test interactions with external services.
3
IntermediateChallenges with real external services
🤔Before reading on: do you think running tests against real external services is always a good idea? Commit to yes or no.
Concept: Why testing directly against real external services can cause problems.
Real external services can be slow, unreliable, or have usage limits. If your tests depend on them, tests might fail due to network issues or service downtime, not your code. Also, tests can be slow and costly if they call real services every time.
Result
You understand why relying on real external services in tests can cause flaky or slow tests.
Knowing these challenges helps you see why we need ways to simulate or control external services during testing.
4
IntermediateUsing mocks and stubs in pytest
🤔Before reading on: do you think mocks replace the real external service completely or just imitate parts? Commit to your answer.
Concept: How to use mocks and stubs to fake external services in tests.
Mocks are fake objects that imitate external services. In pytest, you can use the 'monkeypatch' fixture or 'unittest.mock' to replace real calls with fake ones. For example, mocking a function that calls an API to return a fixed response: import pytest from unittest.mock import Mock def get_data(): # Imagine this calls an external API pass def test_get_data(monkeypatch): mock_response = {'key': 'value'} monkeypatch.setattr('module.get_data', lambda: mock_response) assert get_data() == mock_response
Result
You can write tests that simulate external services without calling them for real.
Using mocks lets tests run fast and reliably by controlling external interactions.
5
IntermediateIntegration testing with real services
🤔Before reading on: do you think integration tests should run as often as unit tests? Commit to your answer.
Concept: When and how to test with real external services in integration tests.
Integration tests check if your software works with real external services. They run less often because they are slower and less reliable. You might use test accounts or sandbox environments provided by the service. These tests catch issues mocks can't, like authentication or data format changes.
Result
You know how to balance fast mock tests with slower real-service integration tests.
Understanding integration tests helps you build confidence that your software works in the real world.
6
AdvancedUsing pytest fixtures for service setup
🤔Before reading on: do you think pytest fixtures can help manage setup and cleanup of external services? Commit to yes or no.
Concept: How pytest fixtures manage setup and teardown for tests involving external services.
Fixtures in pytest let you prepare things before tests run and clean up after. For external services, fixtures can start a local fake server, create test data, or connect to a sandbox. Example fixture: import pytest @pytest.fixture def fake_api_server(): # Start fake server yield # Stop fake server def test_api_call(fake_api_server): # Test code that uses fake_api_server pass
Result
You can organize tests to prepare and clean external service environments automatically.
Fixtures improve test reliability and reduce repeated setup code.
7
ExpertHandling flaky tests from external dependencies
🤔Before reading on: do you think all test failures from external services mean your code is broken? Commit to yes or no.
Concept: Strategies to detect and handle flaky tests caused by unstable external services.
Flaky tests fail sometimes due to network glitches or service issues, not code bugs. Techniques include retrying tests, using timeouts, isolating flaky tests, and monitoring test stability over time. You can mark flaky tests to run separately or skip them temporarily. Example with pytest-rerunfailures plugin: @pytest.mark.flaky(reruns=3) def test_external_call(): # test code pass
Result
You can reduce false alarms and keep your test suite trustworthy despite external instability.
Knowing how to handle flaky tests prevents wasted debugging and maintains team confidence in tests.
Under the Hood
When your test code calls an external service, it usually sends a network request and waits for a response. This involves protocols like HTTP and can be slow or fail. Mocking replaces the real call with a fake function that returns preset data instantly, avoiding network use. Fixtures manage resources by running setup code before tests and cleanup code after, ensuring consistent environments. Flaky tests arise because network and external services are outside your control and can behave unpredictably.
Why designed this way?
Testing external services directly was unreliable and slow, so mocking and fixtures were introduced to isolate tests and speed them up. The design balances realism and speed: mocks for fast, reliable unit tests; real services for thorough integration tests. Handling flaky tests acknowledges that external systems are imperfect and helps maintain test suite health.
┌───────────────┐      ┌─────────────────────┐
│ Test Function │─────▶│ Mocked External Call │
└───────────────┘      └─────────────────────┘
         │                      ▲
         │                      │
         │                      │
         ▼                      │
┌─────────────────┐            │
│ Real External   │────────────┘
│ Service (API)   │
└─────────────────┘

Fixtures:
Setup ──▶ Test ──▶ Teardown
Myth Busters - 4 Common Misconceptions
Quick: Do you think mocking external services means your tests check the real service behavior? Commit to yes or no.
Common Belief:Mocking external services tests the real service behavior exactly.
Tap to reveal reality
Reality:Mocks simulate expected responses but do not test the real service's current behavior or availability.
Why it matters:Relying only on mocks can miss changes or bugs in the real service, causing failures in production.
Quick: Do you think running all tests against real external services is always best? Commit to yes or no.
Common Belief:Testing always against real external services gives the most accurate results.
Tap to reveal reality
Reality:Real-service tests are slower, flaky, and can cause rate limits or costs, so they should be limited and combined with mocks.
Why it matters:Ignoring this leads to slow, unreliable test suites that frustrate developers and delay releases.
Quick: Do you think flaky tests always mean your code is broken? Commit to yes or no.
Common Belief:If a test fails sometimes, it means the code has a bug.
Tap to reveal reality
Reality:Flaky tests often fail due to external factors like network issues, not code bugs.
Why it matters:Misinterpreting flaky tests wastes time chasing non-existent bugs and reduces trust in tests.
Quick: Do you think pytest fixtures only run once per test suite? Commit to yes or no.
Common Belief:Fixtures run only once for all tests.
Tap to reveal reality
Reality:Fixtures can run per test, per module, or per session depending on configuration.
Why it matters:Misunderstanding fixture scope can cause unexpected test behavior or resource leaks.
Expert Zone
1
Mocks should mimic not only data but also error conditions and delays to test robustness.
2
Integration tests often require careful environment management to avoid data pollution and ensure repeatability.
3
Flaky test detection and management is a continuous process involving monitoring, triage, and sometimes test redesign.
When NOT to use
Avoid mocking when you need to verify actual service behavior or contract compliance; use integration or contract testing instead. Also, do not rely on real external services for fast feedback loops; use mocks or simulators. For highly critical systems, consider service virtualization or dedicated test environments.
Production Patterns
In production, teams use layered testing: fast unit tests with mocks for development, scheduled integration tests against sandbox services, and monitoring of flaky tests to maintain quality. Continuous integration pipelines separate these test types and use tagging to control test runs.
Connections
Dependency Injection
Testing with external services often uses dependency injection to replace real services with mocks.
Understanding dependency injection helps you design code that is easier to test with external services by allowing easy swapping of real and fake implementations.
Chaos Engineering
Chaos engineering tests system resilience by intentionally causing failures in external services.
Knowing how to test with external services prepares you to simulate failures and improve system robustness through chaos experiments.
Supply Chain Management
Both involve managing dependencies and ensuring reliable delivery despite external uncertainties.
Recognizing that software testing with external services mirrors supply chain risk management helps appreciate the importance of fallback plans and monitoring.
Common Pitfalls
#1Running all tests against real external services causing slow and flaky tests.
Wrong approach:def test_api_call(): response = real_api_call() assert response.status_code == 200
Correct approach:def test_api_call(monkeypatch): monkeypatch.setattr('module.real_api_call', lambda: MockResponse(200)) response = real_api_call() assert response.status_code == 200
Root cause:Not isolating tests from external dependencies leads to unreliable and slow test runs.
#2Mocking external service but not simulating error cases.
Wrong approach:monkeypatch.setattr('module.get_data', lambda: {'key': 'value'})
Correct approach:def mock_get_data_error(): raise ConnectionError('Service down') monkeypatch.setattr('module.get_data', mock_get_data_error)
Root cause:Ignoring error scenarios in mocks misses testing how code handles failures.
#3Not cleaning up test data in external services causing test pollution.
Wrong approach:def test_create_user(): create_user('test') assert user_exists('test')
Correct approach:@pytest.fixture def cleanup_user(): yield delete_user('test') def test_create_user(cleanup_user): create_user('test') assert user_exists('test')
Root cause:Failing to manage test environment state causes tests to interfere with each other.
Key Takeaways
Testing with external services ensures your software works correctly when interacting with real-world systems.
Mocks and stubs let you simulate external services to keep tests fast, reliable, and isolated.
Integration tests with real services catch issues mocks can't but should be used carefully due to speed and reliability tradeoffs.
pytest fixtures help manage setup and cleanup for tests involving external services, improving test organization.
Handling flaky tests from external dependencies is crucial to maintain trust and efficiency in your test suite.

Practice

(1/5)
1. What is the main reason to use mocking when testing with external services in pytest?
easy
A. To avoid calling the real external service and make tests faster
B. To increase the number of real API calls during testing
C. To make tests dependent on internet speed
D. To test the external service itself

Solution

  1. Step 1: Understand the role of mocking in tests

    Mocking replaces real external calls with fake ones to avoid delays and failures.
  2. Step 2: Identify the benefit of mocking external services

    Mocking makes tests faster and more reliable by not depending on real services.
  3. Final Answer:

    To avoid calling the real external service and make tests faster -> Option A
  4. Quick Check:

    Mocking speeds up tests by faking external calls [OK]
Hint: Mock external calls to speed tests and avoid failures [OK]
Common Mistakes:
  • Thinking mocking increases real API calls
  • Believing tests should depend on internet speed
  • Confusing testing external service with testing your code
2. Which of the following is the correct way to mock a function get_data from module external_api using pytest's patch decorator?
easy
A. @patch('external_api.get_data')
B. @patch('get_data.external_api')
C. @patch('external_api->get_data')
D. @patch('external_api.getData')

Solution

  1. Step 1: Recall correct patch syntax

    The patch decorator requires the full import path as a string: 'module.function'.
  2. Step 2: Match the correct option

    @patch('external_api.get_data') uses 'external_api.get_data' which is the correct format and case-sensitive.
  3. Final Answer:

    @patch('external_api.get_data') -> Option A
  4. Quick Check:

    patch('module.function') syntax is correct [OK]
Hint: Use 'module.function' string in patch decorator [OK]
Common Mistakes:
  • Swapping module and function order
  • Using wrong separators like '->'
  • Incorrect function name casing
3. Given the following pytest test code, what will be the output when running the test?
from unittest.mock import patch
import requests

def fetch_status():
    response = requests.get('https://api.example.com/data')
    return response.status_code

@patch('requests.get')
def test_fetch_status(mock_get):
    mock_get.return_value.status_code = 200
    assert fetch_status() == 200
    print('Test passed')
medium
A. TypeError
B. AssertionError
C. Test passed
D. No output

Solution

  1. Step 1: Understand mocking effect on requests.get

    The patch replaces requests.get with a mock that returns an object with status_code 200.
  2. Step 2: Check assertion and print statement

    fetch_status() returns 200, matching the assertion, so 'Test passed' is printed.
  3. Final Answer:

    Test passed -> Option C
  4. Quick Check:

    Mocked return_value.status_code = 200 makes test pass [OK]
Hint: Mock return_value to control function output [OK]
Common Mistakes:
  • Forgetting to set return_value.status_code
  • Expecting real HTTP call instead of mock
  • Missing print output due to assertion failure
4. Identify the error in the following pytest test that mocks an external service call:
from unittest.mock import patch
import myservice

@patch('myservice.call_api')
def test_api(mock_call):
    mock_call.return_value = {'status': 'ok'}
    result = myservice.call_api()
    assert result.status == 'ok'
medium
A. The patch decorator is missing parentheses
B. The assertion should use result['status'] instead of result.status
C. mock_call.return_value should be a string, not a dict
D. The test function is missing a return statement

Solution

  1. Step 1: Analyze the mocked return value type

    The mock returns a dictionary {'status': 'ok'}, so result is a dict.
  2. Step 2: Check the assertion syntax

    Accessing dict keys requires bracket notation, not dot notation; result.status causes AttributeError.
  3. Final Answer:

    The assertion should use result['status'] instead of result.status -> Option B
  4. Quick Check:

    Dict keys need brackets, not dot notation [OK]
Hint: Use brackets for dict keys in assertions [OK]
Common Mistakes:
  • Using dot notation on dicts
  • Forgetting parentheses in patch decorator (not here)
  • Expecting return_value must be string always
5. You want to test a function process_data() that calls an external API fetch_data(). The API sometimes returns null. How should you mock fetch_data in pytest to test process_data handles null correctly?
hard
A. Do not mock fetch_data; test process_data only with real data
B. Call the real fetch_data to see if it returns null
C. Mock fetch_data to always raise an exception
D. Use patch to make fetch_data return null and assert process_data handles it

Solution

  1. Step 1: Understand the need to test null handling

    Since fetch_data can return null, tests must simulate this to check process_data behavior.
  2. Step 2: Use patch to mock fetch_data returning null

    Mocking fetch_data to return null allows controlled testing of process_data's handling of that case.
  3. Final Answer:

    Use patch to make fetch_data return null and assert process_data handles it -> Option D
  4. Quick Check:

    Mock null return to test edge case handling [OK]
Hint: Mock edge cases like null to test error handling [OK]
Common Mistakes:
  • Calling real external API in tests
  • Mocking only success cases, ignoring null
  • Ignoring exceptions instead of testing them