Integration Tests¶
Паттерн¶
Каждый тест-класс:
_setup()— создаёт данные (chat, member, messages)@patch— мокает WebSocket- Делает HTTP-запрос через
authenticated_client - Проверяет ответ и состояние БД
class TestPinMessageAPI:
async def _setup(self, authenticated_client):
"""Создать чат, участника и сообщение."""
client, user_id, token = authenticated_client
from backend.base.crm.chat.models.chat import Chat
from backend.base.crm.chat.models.chat_message import ChatMessage
from backend.base.crm.chat.models.chat_member import ChatMember
chat_id = await Chat.create(Chat(name="Test Chat"))
await ChatMember.create(
ChatMember(
chat_id=chat_id,
user_id=user_id,
is_active=True,
can_pin=True, # (1)!
)
)
msg_id = await ChatMessage.create(
ChatMessage(
chat_id=chat_id,
body="Pin this",
author_user_id=user_id,
)
)
return client, chat_id, msg_id
@patch(
"backend.base.crm.chat.websocket.chat_manager.send_to_chat",
new_callable=AsyncMock,
)
async def test_pin_message(self, mock_ws, authenticated_client):
client, chat_id, msg_id = await self._setup(authenticated_client)
response = await client.post(
f"/chats/{chat_id}/messages/{msg_id}/pin",
json={"pinned": True},
)
assert response.status_code == 200
assert response.json()["pinned"] is True
mock_ws.assert_called_once() # (2)!
- Не забудь выдать нужные права тестовому участнику.
- Проверяй, что WebSocket-уведомление было отправлено.
Мокирование WebSocket¶
chat_manager.send_to_chat — отправляет события через PubSub (PG LISTEN/NOTIFY или Redis). В тестах мокаем чтобы не зависеть от реальной инфраструктуры:
from unittest.mock import AsyncMock, patch
@patch(
"backend.base.crm.chat.websocket.chat_manager.send_to_chat",
new_callable=AsyncMock,
)
async def test_send_message(self, mock_ws, authenticated_client):
# mock_ws — AsyncMock, перехватывает вызовы send_to_chat
...
# Проверка что WS-событие отправлено с правильными данными
mock_ws.assert_called_once_with(
chat_id=chat_id,
message={
"type": "new_message",
"chat_id": chat_id,
...
},
)
Порядок аргументов с @patch
При использовании @patch как декоратора, mock передаётся первым аргументом после self:
Проверка состояния БД¶
После HTTP-запроса проверяй что данные реально изменились:
@patch(...)
async def test_edit_message(self, mock_ws, authenticated_client):
client, chat_id, msg_id, user_id = await self._setup(authenticated_client)
response = await client.patch(
f"/chats/{chat_id}/messages/{msg_id}",
json={"body": "Edited text"},
)
assert response.status_code == 200
# Проверяем БД напрямую
from backend.base.crm.chat.models.chat_message import ChatMessage
msg = await ChatMessage.get(msg_id)
assert msg.body == "Edited text"
assert msg.is_edited is True
Типичные ошибки¶
Тесты зависают¶
Причина: утечка PubSub LISTEN-соединения из пула.
Решение: убедись что app fixture вызывает stop_services():
@pytest_asyncio.fixture
async def app(test_env):
...
yield app
await test_env.stop_services(app) # ← обязательно
403 в тестах¶
Причина: тестовый ChatMember создан без нужных прав.
Решение: явно указывай права: