Byte Ebi's Logo

Byte Ebi 🍤

A Bit everyday A Byte every week

[Pytest 101] 02 Basic Usage of Mock

How to use Mock to simulate behavior, raise exceptions, and test side effects.

Ray

After covering the basic tools, this article demonstrates how to use mocks effectively in Pytest.
Mocks allow us to replace the logic of real functions to simulate different scenarios and write more precise unit tests.
We’ll walk through examples of mocking functions, simulating errors, and even handling external dependencies like Redis.

Use mocker.patch to replace functions in the same module or in external modules.
For example, to mock the function do_something from service.my_service:

mocker.patch("service.my_service.do_something", return_value="hello")

Mock function

# my_project/service/do_service.py

def do_something():
    try:
        response = something()
        return response
    except Exception as e:
        return str(e)

def something():
    return "success"

We mock the something() function and set its return_value. To verify the mock worked as expected, we return the value directly and use assert to confirm the result.

# my_project/test/service/test_do_service.py

import pytest
from pytest_mock import MockFixture
from service.do_service import (
    do_something,
)

def test_do_something_success(mocker: MockFixture):
    success_message = "success"
    mocker.patch("service.do_service.something", return_value=success_message)

    result = do_something()
    assert result == success_message

Mock function from other module

As projects grow in complexity, it’s common to extract shared functions into separate modules.

Suppose the something() function now lives in service/do_trick.py, and is imported in do_service.py:

# my_project/service/do_trick.py

def something():
    return "success" 
# my_project/service/do_service.py

from service.do_trick import something

def do_something():
    return something()

In this case, the mocking process is slightly different in one crucial way:

When using from package_name import function_name, the imported function becomes part of the current module.

So instead of mocking service.do_trick.something, you must mock service.do_service.something.

If you mock the original module instead, your mock will have no effect—because the function has already been imported into the current scope.

# my_project/test/service/test_do_service.py

import pytest
from pytest_mock import MockFixture
from service.do_service import (
    do_something,
)

def test_do_something(mocker: MockFixture):
    mocker.patch("service.do_service.something", return_value="not success")
    assert do_something() == "not success"

In this example, we mock something() inside service.do_service, not the original service.do_trick.

Mock try-except

When the code includes a try-except block, you can use a mock to simulate an exception being raised.

Set the side_effect of the mocked function to an Exception to simulate an error scenario:

def test_do_something_error(mocker: MockFixture):
    error_message = "Exception error"
    mocker.patch("service.do_service.something", side_effect=Exception(error_message))

    result = do_something()
    assert result == error_message

The return_value attribute only lets you define return values.
For more complex behavior—like raising an exception—use side_effect , which accepts a function, an iterable, or an exception class.

Mock raise Exception

Sometimes the code doesn’t raise errors on its own, but you want to intentionally trigger an exception under certain conditions:

# my_project/service/do_service.py

def do_something(err):
    if err:
        raise Exception(f"Something went wrong")
    return "success"

You can use pytest.raises() to declare the expected exception type and message:

# my_project/test/service/test_do_service.py

import pytest
from pytest_mock import MockFixture
from service.test_do_service import (
    do_something,
)

def test_do_something_success():
    assert do_something(err=False) == "success"

def test_do_something_err():
    with pytest.raises(Exception, match=f"Something went wrong"):
        do_something(err=True)

Mock Redis

Since redis.get returns a bytes object, you need to use encode and decode.

def get_from_redis(id:int):
    # connect to redis
    redis = Redis(host=os.environ["REDIS_HOST"])
    byte_data = redis.get("mykey.{}".format(id))
    data = byte_data.decode("utf-8")

    # set key to redis and expire after 1 hour
    redis.set(
        "mykey.{}".format(id),
        data,
        ex=3600,
    )

    return data
def test_get_from_redis(mocker: MockFixture):
    test_data = "test_data".encode("utf-8")

    mock_redis = mocker.patch("service.do_service.Redis")
    mock_redis_instance = mock_redis.return_value
    mock_redis_instance.get.return_value = test_data
    mock_redis_instance.set.return_value = None

    result = get_from_redis(1)
    assert result == test_data.decode("utf-8")

Recent Posts

Categories

Tags