Mocked Redis counter does not increment between requests for different users

I am testing a Django view that increments recipe view counters using Redis.
The logic is:

  • each user can increment recipe views only once

  • different users should increment the counter

  • Redis is mocked using MagicMock

The first test passes, and part of the second test passes, but the counter never increments to 2 for the second user, even though I explicitly change mock return values.

Django View

# recipehub/apps/recipes/views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, render

from recipehub.apps.recipes.models import Recipe
from recipehub.redis import r

@login_required
def recipe_detail(request, slug):
    recipe = get_object_or_404(Recipe, slug=slug, moderation_status="approved")

    redis_user_recipe_view_key = f"user:{request.user.id}:recipe:{recipe.id}:view"
    redis_all_recipe_view_key = f"recipe:{recipe.id}:views"

    if not r.exists(redis_user_recipe_view_key):
        r.set(redis_user_recipe_view_key, 1)
        r.incr(redis_all_recipe_view_key)

    recipe_views = int(r.get(redis_all_recipe_view_key) or 0)

    return render(
        request,
        "recipes/recipe_detail.html",
        {
            "recipe": recipe,
            "views": recipe_views,
        },
    )

Redis client definition

# recipehub/redis.py
import redis

r = redis.Redis(host="localhost", port=6379, db=0)

**

Redis mock fixture**

import pytest
from unittest.mock import MagicMock

@pytest.fixture()
def mock_redis(mocker):
    magic_mock = MagicMock()
    mocker.patch("recipehub.redis.r", magic_mock)
    return magic_mock

Test 1 — first user increments counter (passes)

@pytest.mark.django_db
def test_recipe_detail_first_user_view_increments_counter(client, users_list, mock_redis):
    recipe = RecipeFactory.create(slug="fish", moderation_status="approved")
    user = users_list["first_simple_user"]

    mock_redis.exists.return_value = False
    mock_redis.get.return_value = b'1'
    mock_redis.incr.return_value = 1
    mock_redis.set.return_value = True

    client.force_login(user)
    response = client.get(
        reverse("recipes:recipe-detail", kwargs={"slug": recipe.slug})
    )

    assertContains(response, '1 views')

Test 2 — different users should increment counter (fails)


@pytest.mark.django_db
def test_recipe_detail_different_users_increment_counter(client, users_list, mock_redis):
    recipe = RecipeFactory.create(slug="fish", moderation_status="approved")
    first_user = users_list["first_simple_user"]
    second_user = users_list["second_simple_user"]

    # First user visit
    mock_redis.exists.return_value = False
    mock_redis.get.return_value = b'1'

    client.force_login(first_user)
    first_response = client.get(
        reverse("recipes:recipe-detail", kwargs={"slug": recipe.slug})
    )
    assertContains(first_response, '1 views')

    # Second user visit
    mock_redis.exists.return_value = False
    mock_redis.get.return_value = b'2'

    client.force_login(second_user)
    second_response = client.get(
        reverse("recipes:recipe-detail", kwargs={"slug": recipe.slug})
    )

    # FAILS: still shows "1 views"
    assertContains(second_response, '2 views')

    assert mock_redis.incr.call_count == 2
    assert mock_redis.set.call_count == 2

Even though I change mock_redis.get.return_value to b'2' before the second request,
the response still shows "1 views".

The incr() method is called twice, but the value returned by get() does not reflect this.

What is the correct way to mock Redis in pytest-django for counters like incr/get? Is MagicMock insufficient for this case?

Вернуться на верх