Skip to content

RedisPlugin Guide

RedisPlugin intercepts redis.Redis.execute_command at the class level. Unlike other stateful plugins, Redis commands carry no inherent ordering constraint, so RedisPlugin extends BasePlugin directly and uses a per-command FIFO queue rather than a session handle with state transitions.

Installation

pip install bigfoot[redis]

This installs redis>=4.0.0.

Setup

In pytest, access RedisPlugin through the bigfoot.redis_mock proxy. It auto-creates the plugin for the current test on first use:

import bigfoot

def test_cache_lookup():
    bigfoot.redis_mock.mock_command("GET", returns="cached_value")

    with bigfoot:
        import redis
        r = redis.Redis()
        value = r.execute_command("GET", "mykey")

    assert value == "cached_value"

    bigfoot.redis_mock.assert_command("GET", args=("mykey",), kwargs={})

For manual use outside pytest, construct RedisPlugin explicitly:

from bigfoot import StrictVerifier
from bigfoot.plugins.redis_plugin import RedisPlugin

verifier = StrictVerifier()
redis_mock = RedisPlugin(verifier)

Each verifier may have at most one RedisPlugin. A second RedisPlugin(verifier) raises ValueError.

Registering mock commands

Use bigfoot.redis_mock.mock_command(command, *, returns, ...) to register a mock before entering the sandbox:

bigfoot.redis_mock.mock_command("SET", returns=True)
bigfoot.redis_mock.mock_command("GET", returns="hello")

Parameters

Parameter Type Default Description
command str required Redis command name, case-insensitive
returns Any required Value to return when this mock is consumed
raises BaseException \| None None Exception to raise instead of returning
required bool True Whether an unused mock causes UnusedMocksError at teardown

Per-command FIFO queues

Each command name has its own independent FIFO queue. Multiple mock_command("GET", ...) calls are consumed in registration order when GET is executed:

def test_multiple_gets():
    bigfoot.redis_mock.mock_command("GET", returns="first")
    bigfoot.redis_mock.mock_command("GET", returns="second")

    with bigfoot:
        r = redis.Redis()
        v1 = r.execute_command("GET", "key1")
        v2 = r.execute_command("GET", "key2")

    assert v1 == "first"
    assert v2 == "second"

    bigfoot.redis_mock.assert_command("GET", args=("key1",), kwargs={})
    bigfoot.redis_mock.assert_command("GET", args=("key2",), kwargs={})

Command names are case-insensitive: mock_command("get", ...) matches execute_command("GET", ...).

Asserting interactions

Use the assert_command helper on bigfoot.redis_mock. All three fields (command, args, kwargs) are required:

assert_command(command, args, kwargs)

bigfoot.redis_mock.assert_command("SET", args=("mykey", "myvalue"), kwargs={})
Parameter Type Default Description
command str required Redis command name (automatically uppercased)
args tuple () Positional arguments passed to execute_command after the command name
kwargs dict \| None None Keyword arguments passed to execute_command (defaults to {})

Simulating errors

Use the raises parameter to simulate Redis errors:

import redis as redis_lib
import bigfoot

def test_redis_error():
    bigfoot.redis_mock.mock_command(
        "GET",
        returns=None,
        raises=redis_lib.exceptions.ResponseError("WRONGTYPE"),
    )

    with bigfoot:
        r = redis.Redis()
        with pytest.raises(redis_lib.exceptions.ResponseError):
            r.execute_command("GET", "badkey")

    bigfoot.redis_mock.assert_command("GET", args=("badkey",), kwargs={})

Full example

import redis
import bigfoot

def increment_counter(r, name):
    current = r.execute_command("GET", name)
    new_val = int(current or 0) + 1
    r.execute_command("SET", name, str(new_val))
    return new_val

def test_increment_counter():
    bigfoot.redis_mock.mock_command("GET", returns="5")
    bigfoot.redis_mock.mock_command("SET", returns=True)

    with bigfoot:
        r = redis.Redis()
        result = increment_counter(r, "visits")

    assert result == 6

    bigfoot.redis_mock.assert_command("GET", args=("visits",), kwargs={})
    bigfoot.redis_mock.assert_command("SET", args=("visits", "6"), kwargs={})

Optional mocks

Mark a mock as optional with required=False:

bigfoot.redis_mock.mock_command("PING", returns="PONG", required=False)

An optional mock that is never triggered does not cause UnusedMocksError at teardown.

UnmockedInteractionError

When code calls execute_command with a command that has no remaining mocks in its queue, bigfoot raises UnmockedInteractionError:

redis.GET(...) was called but no mock was registered.
Register a mock with:
    bigfoot.redis_mock.mock_command('GET', returns=...)