How to Use Pytest for Mock Tests

Mock testing is a technique for isolating the unit of code under test by replacing real dependencies with controlled mock objects. This approach is especially useful when testing code that interacts with external services, such as web servers. Pytest provides built-in support for mocking with tools like pytest-mock and requests-mock.

In this article, you’ll learn how to use Pytest for mock testing, with examples for mocking web servers and testing API integrations.

Why Mock Tests Are Important

Mock tests are essential for:

Setting Up Your Environment

Install the required libraries for mock testing:

pip install pytest-mock requests-mock

Mocking APIs with requests-mock

Example: Mocking a Simple GET Request

Let’s test a function that fetches data from a REST API.

import requests

def fetch_user(user_id):  
    response = requests.get(f"https://api.example.com/users/{user_id}")  
    if response.status_code == 200:  
        return response.json()  
    return None

Test with Mocked Response

Using requests-mock, you can replace the real API call with a mock response.

def test_fetch_user(requests_mock):  
    # Mock the API response  
    requests_mock.get("https://api.example.com/users/1", json={"id": 1, "name": "Alice"}, status_code=200)

    # Test the function  
    result = fetch_user(1)  
    assert result == {"id": 1, "name": "Alice"}  

    # Ensure the API call was made  
    assert requests_mock.called

Explanation:

  1. requests_mock.get: Defines the URL, response body, and status code for the mock request.
  2. requests_mock.called: Verifies that the function made the expected API call.

Mocking POST Requests

For POST requests, use requests_mock.post:

def test_create_user(requests_mock):  
    requests_mock.post("https://api.example.com/users", json={"id": 2, "name": "Bob"}, status_code=201)

    response = requests.post("https://api.example.com/users", json={"name": "Bob"})  
    assert response.json() == {"id": 2, "name": "Bob"}  
    assert response.status_code == 201

Using pytest-mock for General Mocking

While requests-mock is great for HTTP requests, pytest-mock works for mocking any Python object.

Example: Mocking a Web Server with pytest-mock

Imagine testing a function that relies on a third-party server for user authentication:

class AuthService:  
    def authenticate(self, username, password):  
        # Simulate a real server call  
        raise NotImplementedError("Real server interaction not implemented.")

Here’s how you can mock the authenticate method:

def test_authenticate(mocker):  
    auth_service = AuthService()

    # Mock the authenticate method  
    mock_authenticate = mocker.patch.object(auth_service, "authenticate", return_value=True)

    # Test the function  
    result = auth_service.authenticate("user", "password")  
    assert result is True  

    # Ensure the mock was called  
    mock_authenticate.assert_called_once_with("user", "password")

Explanation:

Combining requests-mock and pytest-mock

You can combine requests-mock and pytest-mock to create comprehensive tests that mock both HTTP requests and internal dependencies.

Example: Mocking an API and Logger

import logging

def fetch_and_log_user(user_id):  
    logger = logging.getLogger("app")  
    response = requests.get(f"https://api.example.com/users/{user_id}")  
    if response.status_code == 200:  
        logger.info("User fetched successfully.")  
        return response.json()  
    logger.error("Failed to fetch user.")  
    return None

Test this function with mocked API and logger:

def test_fetch_and_log_user(requests_mock, mocker):  
    # Mock the API response  
    requests_mock.get("https://api.example.com/users/1", json={"id": 1, "name": "Alice"}, status_code=200)

    # Mock the logger  
    mock_logger = mocker.patch("logging.getLogger")

    # Test the function  
    result = fetch_and_log_user(1)  
    assert result == {"id": 1, "name": "Alice"}

    # Verify logger behavior  
    mock_logger().info.assert_called_once_with("User fetched successfully.")

Explanation:

Best Practices for Mock Tests

  1. Focus on Isolation: Mock only the parts of the system that are external or irrelevant to the test.
  2. Verify Interactions: Use assertions to confirm that mocks are called correctly.
  3. Use Fixtures for Reusability: Define reusable mock configurations in Pytest fixtures.
  4. Combine Mocks with Real Tests: Use real endpoints sparingly to validate mocks against actual implementations.

Conclusion

Mock testing with Pytest is a powerful way to isolate and validate your code’s behavior. By leveraging pytest-mock for general mocking and requests-mock for HTTP requests, you can write robust, efficient tests that cover a wide range of scenarios.

Start using these tools in your projects to improve test reliability and maintainability while saving time and resources!