Skip to main content

Overview

Monzoh includes a built-in mock mode that allows you to test your application without making real API calls to the Monzo API. This is particularly useful for:
  • Development: Test your application logic without affecting real accounts
  • Testing: Write unit tests without requiring API credentials
  • Demos: Showcase your application with realistic data
  • Rate limit avoidance: Develop without consuming your API quota

Enabling Mock Mode

Mock mode is activated by using "test" as your access token:
from monzoh import MonzoClient

# Enable mock mode
client = MonzoClient(access_token="test")

# All API calls will now return mock data
accounts = client.accounts.list()
print(f"Mock accounts: {len(accounts)}")
When using mock mode, no real HTTP requests are made to the Monzo API. All responses are generated from predefined mock data.

Mock Data Coverage

Mock mode provides realistic test data for all major API endpoints:

Accounts

client = MonzoClient(access_token="test")

# Returns 2 mock accounts (current and savings)
accounts = client.accounts.list()

for account in accounts:
    print(f"Account: {account.description}")
    print(f"Type: {account.type}")
    print(f"ID: {account.id}")
Mock accounts include:
  • Current account with realistic balance and spend data
  • Savings account with different balance information
  • Proper account IDs and metadata

Transactions

# Returns mock transaction history
transactions = client.transactions.list(
    account_id="acc_mock_current",
    limit=10
)

for transaction in transactions:
    print(f"{transaction.description}: £{transaction.amount / 100:.2f}")
    print(f"Merchant: {transaction.merchant.name if transaction.merchant else 'N/A'}")
Mock transactions include:
  • Various transaction types (purchases, transfers, direct debits)
  • Realistic merchant data with logos and categories
  • Different amounts and descriptions
  • Proper timestamps and metadata

Balance Information

# Returns mock balance data
balance = client.accounts.get_balance(account_id="acc_mock_current")

print(f"Balance: £{balance.balance / 100:.2f}")
print(f"Total balance: £{balance.total_balance / 100:.2f}")
print(f"Spend today: £{balance.spend_today / 100:.2f}")

Pots (Savings)

# Returns mock savings pots
pots = client.pots.list()

for pot in pots:
    print(f"Pot: {pot.name}")
    print(f"Balance: £{pot.balance / 100:.2f}")
    print(f"Goal: £{pot.goal / 100:.2f}" if pot.goal else "No goal set")

Other Endpoints

Mock data is also available for:
  • Webhooks: Mock webhook registration and management
  • Attachments: Simulated file upload responses
  • Receipts: Mock receipt creation and management
  • Feed items: Sample custom feed item creation

Using Mock Mode in Tests

Mock mode is perfect for unit testing:
import pytest
from monzoh import MonzoClient

class TestAccountService:
    @pytest.fixture
    def mock_client(self):
        """Fixture providing a mock Monzo client."""
        return MonzoClient(access_token="test")
    
    def test_get_account_balance(self, mock_client):
        """Test getting account balance with mock data."""
        # Get mock accounts
        accounts = mock_client.accounts.list()
        assert len(accounts) > 0
        
        # Get balance for first account
        balance = mock_client.accounts.get_balance(account_id=accounts[0].id)
        
        # Verify balance structure
        assert balance.balance is not None
        assert balance.currency == "GBP"
        assert balance.spend_today is not None
    
    def test_list_transactions(self, mock_client):
        """Test listing transactions with mock data."""
        accounts = mock_client.accounts.list()
        
        transactions = mock_client.transactions.list(
            account_id=accounts[0].id,
            limit=5
        )
        
        assert len(transactions) <= 5
        for transaction in transactions:
            assert transaction.id is not None
            assert transaction.amount is not None
            assert transaction.description is not None
    
    def test_pot_operations(self, mock_client):
        """Test pot operations with mock data."""
        pots = mock_client.pots.list()
        assert len(pots) > 0
        
        # Test pot deposit (returns success in mock mode)
        result = mock_client.pots.deposit(
            pot_id=pots[0].id,
            account_id="acc_mock_current",
            amount=1000  # £10.00
        )
        assert result is not None

Development Workflow

Use mock mode during development to build your application logic:
import os
from monzoh import MonzoClient

def create_client():
    """Create Monzo client - mock for development, real for production."""
    if os.getenv("ENVIRONMENT") == "development":
        print("Using mock mode for development")
        return MonzoClient(access_token="test")
    else:
        print("Using real API")
        return MonzoClient()

# Your application logic works the same regardless of mode
client = create_client()

# Build your features using the same API
def get_spending_summary(account_id):
    """Get spending summary - works in both mock and real mode."""
    balance = client.accounts.get_balance(account_id=account_id)
    transactions = client.transactions.list(
        account_id=account_id,
        limit=20
    )
    
    return {
        "current_balance": balance.balance / 100,
        "spend_today": balance.spend_today / 100,
        "recent_transactions": len(transactions),
        "largest_transaction": max(
            (abs(t.amount) for t in transactions), 
            default=0
        ) / 100
    }

# Test with mock data
accounts = client.accounts.list()
if accounts:
    summary = get_spending_summary(accounts[0].id)
    print(f"Spending summary: {summary}")

Mock vs Real API Comparison

FeatureMock ModeReal API
API CallsNo HTTP requestsReal HTTP requests
AuthenticationNot requiredOAuth2 required
Rate LimitsNoneStandard Monzo limits
DataPredefined mock dataYour real account data
Side EffectsNoneReal account operations
TestingPerfect for unit testsIntegration tests only

Limitations of Mock Mode

While mock mode is powerful, there are some limitations to be aware of:

1. Static Data

Mock data is predefined and doesn’t change based on operations:
client = MonzoClient(access_token="test")

# Balance won't actually change in mock mode
initial_balance = client.accounts.get_balance("acc_mock_current")
client.pots.deposit("pot_mock_holiday", "acc_mock_current", 1000)
final_balance = client.accounts.get_balance("acc_mock_current")

# initial_balance == final_balance (mock data is static)

2. Limited Error Scenarios

Mock mode doesn’t simulate all possible error conditions:
# Mock mode won't simulate rate limits or network errors
# For comprehensive error testing, use mocking libraries like unittest.mock

from unittest.mock import patch, MagicMock
from monzoh import MonzoClient, MonzoRateLimitError

def test_rate_limit_handling():
    client = MonzoClient(access_token="test")
    
    with patch.object(client, '_request') as mock_request:
        mock_request.side_effect = MonzoRateLimitError("Rate limit exceeded")
        
        # Now you can test error handling
        with pytest.raises(MonzoRateLimitError):
            client.accounts.list()

3. Business Logic Validation

Mock mode doesn’t enforce real-world business rules:
# In mock mode, this might succeed even if it shouldn't
client = MonzoClient(access_token="test")

# Attempting to withdraw more than available balance
# Mock mode won't validate this constraint
client.pots.withdraw("pot_id", "account_id", amount=999999999)

Advanced Mock Testing

For more sophisticated testing scenarios, combine mock mode with testing libraries:
import pytest
from unittest.mock import patch
from monzoh import MonzoClient, MonzoAuthenticationError

class TestMonzoIntegration:
    def test_mock_mode_basic_operations(self):
        """Test basic operations in mock mode."""
        client = MonzoClient(access_token="test")
        
        # Test that all basic operations work
        accounts = client.accounts.list()
        assert len(accounts) > 0
        
        whoami = client.whoami()
        assert whoami.authenticated is True
        
        transactions = client.transactions.list(accounts[0].id)
        assert isinstance(transactions, list)
    
    @patch('monzoh.client.BaseSyncClient._request')
    def test_authentication_failure_simulation(self, mock_request):
        """Test authentication failure with mocked responses."""
        mock_request.side_effect = MonzoAuthenticationError("Invalid token")
        
        client = MonzoClient(access_token="invalid_token")
        
        with pytest.raises(MonzoAuthenticationError):
            client.accounts.list()
    
    def test_mixed_mode_development(self):
        """Test switching between mock and real modes."""
        # Mock client for development
        mock_client = MonzoClient(access_token="test")
        mock_accounts = mock_client.accounts.list()
        assert len(mock_accounts) > 0
        
        # Your application logic should work with both
        def process_accounts(client):
            accounts = client.accounts.list()
            return [{"id": acc.id, "type": acc.type} for acc in accounts]
        
        mock_result = process_accounts(mock_client)
        assert len(mock_result) > 0
        assert all("id" in acc and "type" in acc for acc in mock_result)

Best Practices

1. Environment-Based Mode Selection

import os
from monzoh import MonzoClient

def get_monzo_client():
    """Get appropriate Monzo client based on environment."""
    if os.getenv("MONZO_MOCK_MODE", "false").lower() == "true":
        return MonzoClient(access_token="test")
    else:
        return MonzoClient()

# Set MONZO_MOCK_MODE=true in development
client = get_monzo_client()

2. Feature Flags for Mock Mode

from monzoh import MonzoClient

class MonzoService:
    def __init__(self, use_mock=False):
        self.use_mock = use_mock
        self.client = MonzoClient(
            access_token="test" if use_mock else None
        )
    
    def get_accounts(self):
        accounts = self.client.accounts.list()
        
        if self.use_mock:
            print("📝 Using mock data for development")
        else:
            print("🔐 Using real Monzo API")
        
        return accounts

# Development
dev_service = MonzoService(use_mock=True)

# Production  
prod_service = MonzoService(use_mock=False)

3. Mock Data Validation

def validate_mock_data_structure():
    """Ensure mock data has the same structure as real API responses."""
    client = MonzoClient(access_token="test")
    
    # Validate accounts structure
    accounts = client.accounts.list()
    assert len(accounts) > 0
    
    for account in accounts:
        assert hasattr(account, 'id')
        assert hasattr(account, 'description')
        assert hasattr(account, 'type')
    
    # Validate balance structure
    balance = client.accounts.get_balance(accounts[0].id)
    assert hasattr(balance, 'balance')
    assert hasattr(balance, 'currency')
    assert balance.currency == "GBP"
    
    print("✅ Mock data structure validation passed")

# Run validation during development
if __name__ == "__main__":
    validate_mock_data_structure()
Mock mode is an essential tool for Monzoh development, enabling you to build and test applications efficiently without consuming API quotas or affecting real account data. Use it extensively during development and testing, then switch to the real API for production deployment.
I