How to use mocker.spy in Pytest

Learn how to use mocker.spy in Pytest to track function and method calls without modifying their behavior.

Get Started free
How to use mocker.spy in Pytest
Home Guide How to use mocker.spy in Pytest

How to use mocker.spy in Pytest

When writing tests such as unit, integration, or end-to-end, mocking is an essential technique that helps isolate a function’s behavior by replacing its dependencies with test doubles. This makes it easier to test individual modules without interference, leading to more predictable and accurate results.

Pytest is one of the most widely used testing frameworks in Python, and the pytest-mock library makes mocking easier and more flexible. One of many useful features of this library is mocker.spy. This feature helps you monitor function and method calls in a test without modifying the original functions/ methods.

This article explores how mocker.spy works, highlights real-world use cases, and provides practical code examples to help you use it effectively in your tests.

Understanding Mocking in Pytest

Mocking has always been a crucial part of testing, whether it’s unit, integration, or end-to-end testing. Pytest offers powerful tools for mocking, making it easy to replace real objects with mock versions during tests to isolate behavior and ensure reliable results.

Here are a few of many cases where mocking is useful.:

  • Testing components in isolation without affecting external dependencies.
  • Spying on function calls to verify which arguments they have called with, what values they would return, and their call counts.
  • Simulating different behaviors and edge cases without modifying the actual implementation in the code base.

Common Usecases:

  • Mocking APIs: Replace network requests with mock responses to avoid real API calls.
  • Mocking Database Calls: Prevent tests from interacting with the actual database.
  • Spying on Methods: Track how often a method was called and with what arguments.

Pytest offers the pytest-mock plugin to simplify mocking tasks, by providing powerful tools like mocker.spy, mocker.patch, and mocker.stop for more controlled and readable tests.

What is mocker.spy?

Mocking a function allows testing a module in isolation, but verifying that calls to the mocked function behave as expected is important.  mocker.spy is a pytest-mock utility that helps monitor function and method calls without changing their behavior. It is useful when you need to:

  • Verify how many times a function is called.
  • Check the arguments passed to a function.
  • Ensure a method is executed in a test without modifying its original logic.

For example, if you have a function that logs messages, you need to spy on it to ensure it’s being called correctly:

Python:

import logging



def log_message():

    logging.info("This is a test log.")



def test_log_spy(mocker):

    spy = mocker.spy(logging, "info")

    

    log_message()

    

    spy.assert_called_once_with("This is a test log.")

Output:

mocker spy function example

How mocker.spy Works

As discussed mocker.spy acts as a wrapper around the target function or method, allowing it to behave normally while capturing call details. To understand it better, here’s an overview of its typical workflow.

Workflow of mocker.spy:

  • Attach a spy to a function or method that can be a mocked function or the one you need to monitor.
  • Execute the target function that is under the test.
  • Record details with spy, such as call count, arguments, and return values.
  • Assert with spy to validate the expected behavior.

This makes mocker.spy a powerful tool for verifying function interactions in a non-intrusive way.

Prerequisites for Using mocker.spy

Before using mocker.spy, ensure you have the following installed:

  • Python
  • Pytest
  • Pytest-mock

To install Python you can refer to: https://www.python.org/downloads/.

After installing Python and setting up the system, you can use the below command to install the rest of the libraries.

Command: pip install pytest pytest-mock

Output:

pip install output

Add pytest-mock as a dependency in your project’s requirements.txt or pyproject.toml. This will help you set up prerequisites quickly next time.

Example of mocker.spy Usage

The following example demonstrates how to use mocker.spy to track function calls while preserving their original behavior.

Example 1: Spying on a Class Method

Python:

def test_class_method_spy(mocker):

    class Calculator:

        def add(self, a, b):

            return a + b

    

    calc = Calculator()

    spy = mocker.spy(calc, "add")



    result = calc.add(3, 4)

    
    assert result == 7

    assert spy.call_count == 1

    spy.assert_called_with(3, 4)

Output:

Test passes if add() is called once with (3, 4) and returns 7.

spy a class method

Example 2: Spying on a Standalone Function

Python:

def multiply(x, y):

    return x * y



def test_function_spy(mocker):

        print(__name__)

        spy = mocker.spy(multiply, "__call__")



        assert multiply(2, 5) == 10

        assert spy.call_count == 1

        spy.assert_called_once_with(2, 5)

Output:

Test passes if multiply() is called once with (2, 5) and returns 10.

spy on individual function

Example 3: Spying on an Instance Method in a Mocked Class

Python:

def test_instance_method_spy(mocker):

    class Messenger:

        def send_message(self, message):

            return f"Sent: {message}"

    

    instance = Messenger()

    spy = mocker.spy(instance, "send_message")



    assert instance.send_message("Hello!") == "Sent: Hello!"

    assert spy.call_count == 1

    spy.assert_called_once_with("Hello!")

Output:

Test passes if send_message() is called once with “Hello!”.

spy function in mocked class

When to Use mocker.spy

mocker.spy is useful when you need to track function or method calls without modifying the function’s behavior. It helps to verify the interaction with the function while not interfering with the execution. Here are some key use cases:

1. Verifying Method Calls in Complex Classes

This will ensure that the method is called the correct number of times during the entire execution.

Example: Checking if a specific database query function runs only once with a specific query.

Python:

class Database:

    def fetch_data(self, query):

        return f"Data for {query}"

def test_database_fetch(mocker):

    db = Database()

    spy = mocker.spy(db, "fetch_data")

    db.fetch_data("SELECT * FROM users")

    spy.assert_called_once_with("SELECT * FROM users")

    assert spy.call_count == 1

Output:

Verifying Method Calls in Complex Classes

2. Tracking API calls

Spy can help test and verify that an API client is calling the right endpoint.

Example: Ensuring an HTTP request function is triggered with the correct URL.

Python:

import requests

class APIClient:

    def get_data(self, url):

        return f"Function called with {url}"

def test_api_client(mocker):

    client = APIClient()

    spy = mocker.spy(client, "get_data")

    client.get_data("https://api.example.com/data")

    spy.assert_called_once_with("https://api.example.com/data")

Output:

Tracking API calls

mocker.spy helps verify that an API client is calling the correct endpoint with the expected parameters. This ensures backend integrations behave as intended during testing.

However, to truly ensure reliability, especially in real-world conditions like varying network speeds or browser behavior, tests should go beyond local environments.

BrowserStack makes this possible by running your tests across a wide range of real devices and browsers, helping catch issues that only appear in specific environments. This makes validating API behavior in real user conditions easier before the code reaches production.

BrowserStack Automate Banner

3. Checking Function Execution in Event-Driven Systems

Ensure an error-handling function is called on failure.

Example: Verifying a retry mechanism triggers an error log.

Python:

class TaskProcessor:

    def process(self, task):

        if task == "fail":

            self.handle_error("Task failed")

    def handle_error(self, message):

        print(f"Error: {message}")



def test_retry_mechanism(mocker):

    processor = TaskProcessor()

    spy = mocker.spy(processor, "handle_error")

    processor.process("fail")

    spy.assert_called_once_with("Task failed")

Output:

Checking Function Execution in Event Driven Systems

4. Testing Logging Mechanism

Verify that logs are generated correctly.

Example: Ensuring an info log message is recorded.

Python:

import logging

def log_message():

    logging.info("This is a test log.")

def test_log_spy(mocker):

    spy = mocker.spy(logging, "info")

    log_message()

    spy.assert_called_once_with("This is a test log.")

Output:

Testing Logging Mechanism

mocker.spy helps validate internal logic, but real-world behavior can vary across browsers and devices. Running Pytest tests on BrowserStack Automate ensures consistent performance in actual environments.

Talk to an Expert

When Not to Use mocker.spy

Use alternative methods in these cases:

ScenarioBetter Alternative
You need to completely replace a functionmocker.patch
The function makes external calls that shouldn’t runmocker.patch with a return value
You want to return controlled outputsmocker.Mock

Combining mocker.spy with Other Pytest-Mock Features

mocker.spy is already powerful on its own, but to test a function with all possible use cases, it must be combined with other pytest-mock features, which can enhance your test coverage.

1. Combining mocker.spy with mocker.patch

When you want to track how a function is called but also want to change its behavior, mocker.patch is the feature to use.

Example: Mock an API call while still tracking its usage.

Python

import requests



class APIClient:

    def get_data(self, url):

        return requests.get(url).json()



def test_spy_with_patch(mocker):

    client = APIClient()

    

    # Patch the method to return a controlled response

    mocker.patch.object(client, "get_data", return_value={"data": "mocked response"})



    # Spy on the method

    spy = mocker.spy(client, "get_data")

    
    result = client.get_data("https://api.example.com/data")



    assert result == {"data": "mocked response"}  # Ensures the function returns the patched response

    spy.assert_called_once_with("https://api.example.com/data")  # Ensures the function was called

Output:

Combining mocker.spy with mocker.patch

2. Combining mocker.spy with mocker.stub

mocker.stub helps you fake a function that mimics real behavior, and to track this mimicked function, you can use mocker.spy. The stub is a mock object that accepts any arguments and is useful to test callbacks.

Example: Create a stub function and spy on its calls.

def send_email(callback_func):

    return callback_func("test@example.com", "Hello")

    


def test_spy_with_stub(mocker):

    # Create a stub function

    stub = mocker.stub(name="send_email_stub")

    

    # Spy on the stub function

    spy = mocker.spy(send_email, "__call__")



    # Call the stub function

    response = send_email(stub)

    stub.assert_called_once_with("test@example.com", "Hello")  # Ensures it was called correctly

Output:

Combining mocker.spy with mocker.stub

3. Combining mocker.spy with mocker.stop

You often want to spy on a function temporarily and stop tracking it to prevent interference with later assertion. mocker.stop provides this capability to remove any spy on a function.

Example: Spy on a logging function but stop tracking it midway.

Python

import logging

def log_message():

    logging.info("Test log message.")

def test_spy_with_unspy(mocker):

    # Spy on logging.info

    spy = mocker.spy(logging, "info")

    log_message()  # Call the function

    spy.assert_called_once_with("Test log message.")  # Verify the spy tracked it



    # Unspy the function

    mocker.stop(spy)



    log_message()  # Call the function again



    assert spy.call_count == 1  # Ensures the second call is NOT tracked

Output:

Combining mocker.spy with mocker.stop

Conclusion

Spying on functions with mocker.spy is an excellent way to track function behavior without modifying implementation. Whether you’re testing class methods, standalone functions, or instance methods, mocker.spy provides a powerful and flexible approach to verifying function calls.

To ensure this verified behavior holds true across real-world conditions, run your automated tests on BrowserStack. It gives you access to thousands of real devices and browsers, helping you catch environment-specific issues early and deliver reliable experiences to every user.

Try BrowserStack Now

Tags
Automation Testing Website Testing

Get answers on our Discord Community

Join our Discord community to connect with others! Get your questions answered and stay informed.

Join Discord Community
Discord