What Are Pytest Mock Assert Called Methods and How To Leverage Them

Writing code without tests it is like delivering a half-baked cake.

Mocking plays a crucial role in unit testing by isolating code under test from its dependencies, such as external services, APIs, or complex libraries.

While mocking is super helpful, how do you ensure that the code under test interacts correctly with all its dependencies? After all, it’s not possible to mock each and every function. Not only would it be messy, but incredibly hard to maintain and debug.

This is where Pytest Mock Assert Calls come into play. It allows you to assert method calls on mock objects, ensuring that the code under test interacts correctly with its dependencies.

In this article, we’ll learn how to ensure that the code being tested communicates effectively with its dependencies by asserting method calls.

We’ll also explain the different types of assertions available in Pytest Mock Assert Calls and how they can be used to test your code.

These assertions help guarantee that the code interacts correctly and achieves the expected behavior, even when external dependencies are replaced with mock objects.

You’ll get a deep understanding of pytest mock assertion calls and their role in unit testing.

Let’s get started, then!

Link To GitHub Repo

The Need for Asserting Method Calls

How can you ensure that your code under examination interacts correctly with its dependencies, arguments, and APIs? Well, asserting method calls is one of the best ways to do it.

It guarantees that methods are called with the right arguments in the expected sequence, or not called at all, and that the code behaves as intended.

Without proper assertions, you run the risk of missing critical interactions or failing to detect regressions when code changes.

Moreover, we know that unit tests serve as a safety net for your codebase, catching bugs early in the development process. If your unit tests lack robust assertions on method calls, they become less reliable, and you might miss important issues.

If you still underestimate the importance of asserting method calls, let’s look at an example:

In applications that interact with databases, asserting method calls can verify that queries and data manipulation are performed as expected, preventing data corruption or accidental changes to the database schema.

Imagine if the database we are talking about is a banking database. How big of a havoc that will be?

What You’ll Learn

By the end of this tutorial, you will

  • Understand Pytest Mock Assert Calls and their importance in unit testing.
  • Learn how to use Pytest to create and assert method calls with mocks.
  • Uncover best practices for writing effective test cases that involve method call assertions.
  • Explore advanced techniques, such as custom assertion methods and dynamic assertion calls, to enhance testing capabilities.
  • Discuss potential pitfalls and common mistakes to avoid when using Pytest Mock Assert Calls.

Project Set Up

Getting Started

The project has the following structure:

pytest mock assert repo

Simple repo with a couple of test files explaining the key concepts.

Prerequisites

To achieve the above objectives, the following is recommended:

  • Basic knowledge of the Python programming language
  • Basics of Pytest Unit Testing

To get started, clone the repo here.

Basics of Pytest Mocking

The Pytest-mock plugin extends Pytest’s capabilities to include mocking (built off unittest.mock), making it easier to isolate code units and verify their interactions.

If you want to learn more about Pytest mocking, then give this guide a read.

To use the pytest-mock plugin, you need to ensure it is installed in your Python environment. You can install it using pip:

1
pip install pytest-mock

Once installed, you can leverage the mocker fixture provided by pytest-mock for mocking within your tests.

If for any reason you get the dreaded fixture "mocker" not found" error - it’s likely the plugin is not installed correctly. We covered that more in detail here.

The mocker fixture allows you to create mock objects, patch methods and attributes, and set return values for these mocks.

Let’s discuss how to create objects and patch attributes using a sample code.

tests/unit/test_pytest_mock_assert_basic.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pytest
from unittest.mock import Mock, patch

def get_total_price(price, quantity):
return price * quantity

def test_get_total_price():
# Create a mock object
mock_calculator = Mock()

# Patch the 'get_total_price' function to use the mock_calculator
with patch(__name__ + '.get_total_price', side_effect=mock_calculator) as mocked_function:
# Set the return value for the mock_calculator
mock_calculator.return_value = 25

# Call the function under test
result = get_total_price(5, 5)

# Verify that the mock_calculator was called with the correct arguments
mocked_function.assert_called_once_with(5, 5)

# Verify the result of the function
assert result == 25 # The result is the correct total cost (5 * 5)

pytest mock assert

In the test function, we create a mock object called mock_calculator using Mock(). This mock object will replace the get_total_price function during the test.

Next, we have used the @patch decorator to temporarily replace the get_total_price function with our mock object mock_calculator- this is what isolation of code means.

The side_effect parameter allows you to specify a function, object or even exception that will be called when the patched function or method is invoked.

In our case, the side_effect parameter is set to the mock object, meaning that calls to get_total_price will be handled by the mock_calculator mock.

We set the return value for the mock_calculator using mock_calculator.return_value. In this case, we set it to 25, which is the expected result of the calculation.

Last step before running our test code is to use assert_called_once_with to verify that the mock object mock_calculator is called with the correct arguments, which are (5, 5).

Pytest Mock Assert Call - The Key Concepts

Pytest-Mock provides various assertion methods, including

  • assert_called
  • assert_called_once
  • assert_called_once_with
  • assert_called_with

and others. We’ll study all these methods in detail below. You can read more about the various assert methods of the Unittest.Mock library here.

These methods allow you to verify that a method on a mock object was called with the correct arguments during a test.

Moreover, these assertion methods help you ensure that the code under test interacts correctly with its dependencies using custom assertion calls.

assert_called

Basically, can use the assert_called method to verify that a specific method on a mock object has been called during a test. No matter how many times it’s called, it should have been atleast called once.

Here’s what the boilerplate code looks like:

1
2
3
4
5
6
7
8
9
10
11
import pytest
from unittest.mock import Mock

# Create a mock object
sample_mock = Mock()

# Call a method on the mock object
sample_mock.some_method(10)

# Use assert_called to check if the method was called
sample_mock.some_method.assert_called()

Here we are checking if the some_method was called. If the method was called at least once, the assertion will pass; otherwise, it will raise an AssertionError.

assert_called_once

In some situations, you might just want to call a specific function on a mock object only once. This can be done using assert_called_once.

1
2
3
4
5
6
7
8
9
10
11
import pytest
from unittest.mock import Mock

# Create a mock object
sample_mock = Mock()

# Call a method on the mock object
sample_mock.some_method(10)

# Use assert_called to check if the method was called
sample_mock.some_method.assert_called_once()

We use assert_called_once to check if the some_method was called exactly once. If the method is called only one time, the assertion will pass; otherwise, it will raise an AssertionError.

assert_called_once_with

However, if you want to verify that a specific method on a mock object was called but also that it was called exactly once and with specific arguments, then you should use assert_called_once_with.

1
2
3
4
5
6
7
8
9
10
11
import pytest
from unittest.mock import Mock

# Create a mock object
sample_mock = Mock()

# Call a method on the mock object
sample_mock.some_method(10)

# Use assert_called to check if the method was called
sample_mock.some_method.assert_called_once_with(10)

Now, if some_method is called only once and with 10 as its argument, only will our assertion pass.

assert_called_with

Let’s imagine that you don’t care how many times a specific method is called, but you want to ensure that every time the method is called, it is called with some fixed arguments. Well, then you use assert_called_with.

1
2
3
4
5
6
7
8
9
10
11
import pytest
from unittest.mock import Mock

# Create a mock object
sample_mock = Mock()

# Call a method on the mock object with specific arguments
sample_mock.some_method(10, 20)

# Use assert_called_with to check if the method was called with the expected arguments
sample_mock.some_method.assert_called_with(10, 20)

In the above snippet, assert_called_with is used to check if the some_method was called with the expected arguments (10 and 20). If the method is called with these arguments, the assertion will pass; otherwise, it will raise an AssertionError.

Keep in mind that you can use these assertions together in your tests to comprehensively verify both the call count and the specific arguments passed to a method.

assert_not_called

The name of this method is self-explanatory - to verify that a specific method on a mock object was not executed during a test.

This assertion is valuable for ensuring that certain parts of your code were not called, helping you detect unexpected or unwanted behavior.

Let’s see how this is used:

1
2
3
4
5
6
7
8
import pytest
from unittest.mock import Mock

# Create a mock object
sample_mock = Mock()

# Use assert_not_called to check if the method was not called
sample_mock.some_method.assert_not_called()

Now, we know that the some_method will never be called during the execution of the code.

If you want to understand the use of this assertion call, then let’s take an example. You have a website, and you want a user to log in to their account to use your website services.

Without login, the user should not gain access to your website, so you will call assert_not_called for your login method to check if your website behaves correctly and avoids unwanted side effects when certain conditions are not met.

assert_has_calls

assert_has_calls is a useful method in Pytest-Mock for asserting the order in which methods were called on a mock object.

This can be important in scenarios where the sequence of method calls is critical to the behavior of your code.

Suppose you have a Python program for a coffee shop’s order processing system. You want to ensure that orders are processed in the correct sequence, which involves taking orders, preparing drinks, and serving them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pytest
from unittest.mock import Mock, call

# Create a mock object
sample_mock = Mock()

# Perform a sequence of method calls
sample_mock.some_method(10)
sample_mock.another_method("hello")

# Define the expected call sequence
expected_calls = [call.some_method(10), call.another_method("hello")]

# Use assert_has_calls to check if the calls match the expected sequence
sample_mock.assert_has_calls(expected_calls)

assert_has_calls has been used to check if the actual calls on the mock object match the expected call sequence. This means that first, some_method(10) will run, and then another_method("hello") will be executed.

If the calls match, the assertion will pass; otherwise, it will raise an AssertionError.

assert_any_call

Now let’s study when to use assert_any_call.

Use it to verify that a specific method on a mock object has been called at least once during a test with the correct arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pytest
from unittest.mock import Mock

# Create a mock object
my_mock = Mock()

# Call the method on the mock object with different arguments
my_mock.some_method(10)
my_mock.some_method(20)
my_mock.some_method(30)

# Use assert_any_call to check if the method was called with any set of arguments
my_mock.some_method.assert_any_call(20) # This will pass
my_mock.some_method.assert_any_call(40) # This will fail

Now, we have used assert_any_call to check if the some_method was called with any set of arguments.

In this case, the first assertion will pass because some_method was called with the argument 20, and the second assertion will fail because there is no call with the argument 40.

You can use assert_any_call and assert-called synonymously, but it depends on what you want to confirm in your test.

Example Code

Now that you have learned about all the assertion calls, let’s put them together to test in a sample code.

tests/unit/test_pytest_mock_assert_example.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from unittest.mock import Mock, call

def get_total_price(price, quantity):
return price * quantity

def test_get_total_price():
# Create a mock object
my_mock = Mock()

# Call the method on the mock object
my_mock.add(5, 5)
my_mock.subtract(10, 2)

# Use assertion methods to verify method calls
my_mock.add.assert_called()
my_mock.add.assert_called_once()
my_mock.add.assert_called_with(5, 5)
my_mock.add.assert_called_once_with(5, 5)
my_mock.subtract.assert_any_call(10, 2)
my_mock.assert_has_calls([call.add(5, 5), call.subtract(10, 2)], any_order=True)
my_mock.some_method.assert_not_called()

pytest mock assert

As you can see in the output, all the assertions passed. You can refer back to the previous section to understand how each method call works.

Frequently Asked Questions

Are mock and MagicMock the Same Methods?

No, they are not the same. The Mock class in Python’s unittest.mock library is used to mock objects.

A mock object simulates the behavior of the object it replaces by creating attributes and methods on-the-fly.

In other words, you can access any attribute or method on a mock object, and if it doesn’t exist, the mock object will dynamically create it.

In contrast, the MagicMock class is an extension of the Mock class.

It has all the capabilities of a regular Mock, but with the addition of most of the “magic” or “dunder” methods pre-created. These are special methods in Python, like __getitem__ or __len__, that enable custom behaviors for common operations.

If you need a mock object to emulate special methods or operations, using MagicMock is often more convenient than using a basic Mock.

Dealing with Dynamic Call Counts

How do you deal with dynamic call counts in your unit tests? Well, programmers use pytest-mock-assert-calls to assert and validate method calls on mock objects with flexibility.

Whether you expect a method to be called once, multiple times, or not at all, pytest-mock-assert-calls enables you to write tests that adapt to the dynamic nature of your code.

It provides the essential structure to create robust, adaptable unit tests that accurately reflect your code’s behavior in real-world scenarios.

Overcoming Challenges with Async Code

Asynchronous code, often referred to as async code, is a programming paradigm that allows a program to perform multiple tasks concurrently without waiting for each task to complete before moving on to the next one.

When dealing with asynchronous code in Python, testing can become challenging due to the inherent non-deterministic nature of asynchronous operations.

However, pytest-mock-assert-calls can help overcome these challenges. By using this library in combination with Pytest, you can create mock objects and assert method calls in a structured and reliable manner.

This allows you to maintain control over the interactions with dependencies, ensuring that your asynchronous code functions correctly while being thoroughly tested. Great Save, right?

If you want to dig deeper into the arena of async code and how to test it in your program, then this comprehensive guide is all that you need!

Best Practices and Tips

When using pytest-mock-assert-calls in your tests, there are several best practices and tips you must know to ensure effective testing and maintainable code. Let’s discuss a few of them!

Naming Conventions for Mock Objects

When naming mock objects, adhere to meaningful and descriptive names that reflect the purpose of the mock.

For example, if you’re mocking a database connection, you could name it mock_db_connection. This makes your tests more readable and helps other developers understand the test’s intent.

Isolating Test Cases

Isolating test cases when using pytest-mock-assert-calls is crucial to ensure that each test runs independently without interference from previous or subsequent tests.

Proper isolation helps maintain the reliability and reproducibility of your tests. Do not use global variables for shared resources between test cases.

If your tests create or modify resources, ensure that you clean up after each test. For example, if your mock objects store state or side effects, reset them at the end of the test using the reset method.

1
2
def test_something(my_mock):
my_mock.reset() # Clean up any state or side effects

Alternatively, you can use Pytest fixtures to manage mocks’ lifecycles. Pytest automatically handles fixture setup and teardown, ensuring proper cleanup.

You can also define Pytest fixture scopes to fine-tune the lifecycle of your mocks. For example, you can use the function scope to create a new mock object for each test function.

Keeping your Tests DRY

Don’t get it wrong, but keep your tests as DRY (Don’t Repeat Yourself) as possible.

This means writing reusable utility functions or fixtures for common setup and assertions. Use Pytest fixture functions to set up common resources, including mock objects, that can be reused across multiple test functions.

This reduces redundancy and makes your test suite more maintainable while using pytest-mock-assert-calls to assert method calls on your mock objects.

When to Use Mocking and When to Avoid It

When you want to test the actual interaction between your code and external dependencies, consider integration testing instead of mocking.

Mocking is often more applicable to high-level or complex code. Avoid excessive mocking. Only mock what’s necessary to isolate the code under test. Over-mocking can lead to complex, hard-to-maintain tests.

When conducting performance or load testing, you can use mocks to simulate external systems, making it easier to measure the performance of the code under test without affecting real resources.

Common Mocking Problems

Classes and function definitions change all the time. When the interface of an object changes, any tests relying on a Mock of that object may become irrelevant.

For example, you rename a method but forget that a test mocks that method and invokes assert_not_called(). After the change, assert_not_called() is still True. The assertion is not useful, though, because the method no longer exists.

A problem specific to Mock is that a misspelling can break a test. Recall that a Mock creates its interface when you access its members. So, you will inadvertently create a new attribute if you misspell its name.

How to Handle an AssertionError?

When an AssertionError occurs, it indicates that the expected behavior of the code under test does not match the actual behavior.

If the AssertionError is related to method call assertions on mock objects, review the mock configurations to ensure they match the actual calls in your code.

This is the most frequent reason for an AssertionError. For example, you mock a method, call it and then use assert_not_called- obviously this will give an AssertionError.

So, every time you get an AssertionError, read the above section on pytest-mock-assert-calls again and try to solve the AssertionError yourself.

Also, ensure that you haven’t overlooked mocking specific methods or that you haven’t over-mocked, which can lead to missed assertions or false negatives.

Neglecting Call Order Assertions

Neglecting call order assertions may result in incomplete test coverage, as you may not account for the specific sequence of method calls that your code relies on.

Without proper call order assertions, tests may pass even if the code’s logic is incorrect. This can lead to false positives, where a test provides a false sense of confidence in the code’s correctness.

Suppose you’re developing a shopping cart feature. The cart allows users to add and remove items, and it calculates the total price based on the items added.

Now imagine, you asserted to calculate the total first and then asserted the method call of adding items price in the total.

The functionality of the shopping cart hasn’t changed, but the order of method calls on the mock objects has.

Without call order assertions, the test continues to pass, even though the method call order is incorrect. This can lead to false confidence in the correctness of the code.

Conclusion and Learnings

OK, that’s a wrap.

This article taught us some important concepts of mocking and raising exceptions.

First, we explored what Method Assertion Call is and why is it such a life-saver.

Then we looked at the basics of pytest-mock-assert-calls, its different methods and how they are used.

Lastly, we looked at some common mistakes made while using Pytest mock assertion calls and how to avoid them.

Mocking and assertion calls are extremely important concepts in unit testing, and your ability to master them will give you the freedom to test your code isolated from even the most complex dependencies.

If you have ideas for improvement or would like me to cover anything specific, please send me a message via Twitter, GitHub or Email.

Till the next time… Cheers!

Additional Learning

Link to Example Code Repo
Official Docs - unittest.mock — mock object library
Introduction to Pytest Mocking - What It Is and Why You Need It
How To Easily Resolve The “Fixture ‘Mocker’ Not Found” Error in Pytest
How To Test Raised Exceptions with Pytest MagicMock? (Advanced Guide)
3 Ways To Easily Test Your Python Async Functions With Pytest Asyncio
What is Setup and Teardown in Pytest? (Importance of a Clean Test Environment)
What Are Pytest Fixture Scopes? (How To Choose The Best Scope For Your Test)
The Ultimate Guide To Using Pytest Monkeypatch with 2 Code Examples