Guide: Pytest Integration

Set up Receiver.email as a Pytest fixture for async email testing. This guide covers installation, fixtures, and example tests.

Prerequisites

bash
pip install receiverdotemail pytest pytest-asyncio

Create a Fixture

Add a shared fixture in conftest.py:

conftest.py
import os
import pytest
from receiverdotemail import ReceiverClient

@pytest.fixture
async def receiver():
    """Shared Receiver.email client fixture."""
    async with ReceiverClient(
        api_key=os.environ["RECEIVER_API_KEY"]
    ) as client:
        yield client

@pytest.fixture
def project_id():
    """Project ID for email testing."""
    return os.environ.get("RECEIVER_PROJECT_ID", "proj_abc123")

@pytest.fixture
def email_suffix():
    """Address suffix for the test project: any prefix + suffix is a valid inbox."""
    return os.environ.get("RECEIVER_EMAIL_SUFFIX", "[email protected]")

Write Tests

test_emails.py
import re
import time
import pytest

@pytest.mark.asyncio
async def test_welcome_email(receiver, project_id, email_suffix):
    """Test that signup sends a welcome email."""
    test_addr = f"signup-{int(time.time())}{email_suffix}"

    # Trigger your app to send a welcome email
    await your_app.signup(email=test_addr, password="test123")

    # Wait for the email
    email = await receiver.emails.wait_for(
        project_id=project_id,
        recipient=test_addr,
        timeout=30,
    )

    assert email is not None
    assert "welcome" in email.subject.lower()
    assert len(email.links) > 0

@pytest.mark.asyncio
async def test_password_reset(receiver, project_id, email_suffix):
    """Test password reset email contains a valid link."""
    test_addr = f"reset-{int(time.time())}{email_suffix}"

    await your_app.request_password_reset(test_addr)

    email = await receiver.emails.wait_for(
        project_id=project_id,
        recipient=test_addr,
        timeout=30,
    )

    assert email is not None
    reset_link = next(
        (l for l in email.links if "/reset" in l), None
    )
    assert reset_link is not None

@pytest.mark.asyncio
async def test_otp_code(receiver, project_id, email_suffix):
    """Test OTP email contains a 6-digit code."""
    test_addr = f"otp-{int(time.time())}{email_suffix}"

    await your_app.send_otp(test_addr)

    email = await receiver.emails.wait_for(
        project_id=project_id,
        recipient=test_addr,
        timeout=30,
    )

    assert email is not None
    otp_match = re.search(r"\d{6}", email.subject)
    assert otp_match is not None
    assert len(otp_match.group(0)) == 6

Configure Pytest

Set up pytest.ini or pyproject.toml for async support and appropriate timeouts:

pytest.ini
[pytest]
asyncio_mode = auto
timeout = 60

Run Tests

bash
# Set your API key
export RECEIVER_API_KEY="recv_live_..."

# Run tests
pytest tests/test_emails.py -v

# Run with coverage
pytest tests/test_emails.py --cov=your_app -v

Tips

  • Use asyncio_mode = auto in pytest config so you don't need to mark every test with @pytest.mark.asyncio.
  • Increase timeout: Email tests involve network I/O. Set pytest timeout to at least 60 seconds.
  • Unique addresses: Use timestamps in addresses to prevent test interference.
  • Environment variables: Store API key and project ID as env vars, not in code.

Next Steps