unittest mock examples

Getting Started with Unittest Mock

What is Unittest Mock?

Imagine you have a function that calls a method on an object. You want to test that the method was called with the correct arguments. But you don't want to create the object because setting it up might be complicated.

That's where unittest mock comes in. It lets you create a fake object that behaves like the real object. You can make it do anything you want, like return specific values or raise exceptions. This way, you can test your function without relying on the real object being there.

What Can You Do with Unittest Mock?

  • Mock Classes: Create fake versions of classes and control how they are used in your code.

  • Mock Methods: Create fake versions of methods and track how they are called.

  • Mock Functions: Create fake versions of functions and control their behavior.

  • Patch Objects: Replace real objects with fake objects in your code.

How to Use Unittest Mock

  1. Import mock: import unittest.mock

  2. Create a Mock: For example, mock = mock.Mock()

  3. Configure the Mock: Set up what the mock will do when called, such as returning a value or raising an exception.

  4. Use the Mock: Pass the mock to your code under test.

Real-World Applications

  • Testing database interactions: Mock the database to control the behavior of database operations.

  • Testing REST APIs: Mock the HTTP client to control the requests and responses.

  • Testing asynchronous code: Mock the event loop to control the timing and order of events.

Simplified Example

Imagine you have a function that calculates the area of a rectangle.

def calculate_area(width, height):
    return width * height

You want to test that the function works correctly, but you don't want to create a rectangle object. You can mock the rectangle object and set up its methods to return the correct values:

import unittest.mock

class TestCalculateArea(unittest.TestCase):

    def test_calculate_area(self):
        # Create a mock rectangle object
        mock_rectangle = unittest.mock.Mock()

        # Configure the mock rectangle to return the correct values
        mock_rectangle.width = 5
        mock_rectangle.height = 10

        # Pass the mock rectangle to the calculate_area function
        area = calculate_area(mock_rectangle.width, mock_rectangle.height)

        # Assert that the area is correct
        self.assertEqual(area, 50)

Note: In this example, we directly access mock_rectangle.width and mock_rectangle.height. You can also use mock_rectangle.width() and mock_rectangle.height() to call the methods on the mock, which might be necessary in some cases.


Mocking functions with patch

The patch function is a decorator that allows you to replace a function or object with a mock object during the execution of a test. This can be useful for testing code that relies on external dependencies, such as databases or web services.

For example, the following code patches out the requests library so that it returns a mock response object when the get function is called:

import unittest
import mock

class MyTest(unittest.TestCase):

    @patch('requests.get')
    def test_get_request(self, mock_get):
        mock_get.return_value = mock.Mock(status_code=200)
        response = requests.get('http://example.com')
        self.assertEqual(response.status_code, 200)

In this test, the mock_get parameter is a mock object that replaces the requests.get function. The return_value attribute of the mock object is set to a mock response object with a status code of 200. When the requests.get function is called in the test, it will return the mock response object instead of making a real HTTP request.

Mocking classes with patch

The patch function can also be used to patch out classes. This can be useful for testing code that relies on specific class methods or attributes.

For example, the following code patches out the Foo class so that it returns a mock object when the bar method is called:

import unittest
import mock

class MyTest(unittest.TestCase):

    @patch('mymodule.Foo')
    def test_foo_bar(self, mock_foo):
        mock_foo.return_value.bar.return_value = 'baz'
        foo = mymodule.Foo()
        self.assertEqual(foo.bar(), 'baz')

In this test, the mock_foo parameter is a mock object that replaces the mymodule.Foo class. The return_value attribute of the mock object is set to a mock object that represents an instance of the Foo class. The bar attribute of the mock object is set to a mock function that returns the value 'baz'. When the bar method is called on the foo object in the test, it will call the mock function and return the value 'baz'.

Managing patches

There are two ways to manage patches:

  • Context manager: The patch function can be used as a context manager. This allows you to patch out a function or object for the duration of the context. For example:

with patch('requests.get') as mock_get:
    mock_get.return_value = mock.Mock(status_code=200)
    response = requests.get('http://example.com')
    self.assertEqual(response.status_code, 200)
  • Decorator: The patch function can also be used as a decorator. This allows you to patch out a function or object for the duration of the decorated function. For example:

@patch('requests.get')
def test_get_request(self, mock_get):
    mock_get.return_value = mock.Mock(status_code=200)
    response = requests.get('http://example.com')
    self.assertEqual(response.status_code, 200)

Real world applications

Mocking is a powerful tool that can be used to test code in a variety of ways. Here are some real world applications of mocking:

  • Testing code that relies on external dependencies: Mocking can be used to test code that relies on external dependencies, such as databases or web services. This can be useful for testing the code in a controlled environment without having to worry about the availability or performance of the external dependency.

  • Testing code that is difficult to test: Mocking can be used to test code that is difficult to test, such as code that interacts with the operating system or that uses a lot of threads. This can be useful for testing the code without having to worry about the side effects of the code.

  • Testing code that is expensive to test: Mocking can be used to test code that is expensive to test, such as code that requires a lot of setup or that takes a long time to run. This can be useful for testing the code without having to spend a lot of time and resources.

Potential applications in real world

  • Unit testing of components that interact with external systems, such as databases, web services or file systems.

  • Integration testing of systems that interact with each other.

  • Mocking out specific behavior of a class or object to test how other code responds to it.

  • Replacing complex dependencies with simpler mocks to make testing a certain aspect of the code easier.

  • Testing how a component handles errors or edge cases by mocking out the failure behavior.

  • Isolating the behavior of a specific function or method to test it independently of the rest of the system.