Testing

Test your custody integration using mocking and testing best practices.

What This Section Covers

Testing patterns, mocking the Custody SDK, pytest fixtures, and best practices for testing applications that use the Custody SDK.

Testing with Mocks

Use unittest.mock or pytest-mock to mock SDK responses:

pytest Example

import pytest
from unittest.mock import MagicMock
from vecu_custody import CustodyClient
from vecu_custody.models import Authorization, AuthorizationStatus

@pytest.fixture
def mock_client(mocker):
    """Provide a mocked custody client."""
    client = MagicMock(spec=CustodyClient)

    # Mock authorization creation
    mock_auth = Authorization(
        authorization_id="AUTH-12345678",
        vin="9HGBH41JXMN999999",
        status=AuthorizationStatus.PENDING,
        origin="LOC-AUCTION-MANHEIM-ATLANTA",
        destination="LOC-DEALERSHIP-CARMAX-ORLANDO",
        person_identity_key="vecu_test123",
        role="DRIVER",
        created_at="2026-01-23T10:30:00Z",
        expires_at="2026-01-24T10:30:00Z"
    )
    client.authorizations.create.return_value = mock_auth

    return client

def test_create_authorization(mock_client):
    authorization = mock_client.authorizations.create(
        vin="9HGBH41JXMN999999",
        origin="LOC-AUCTION-MANHEIM-ATLANTA",
        destination="LOC-DEALERSHIP-CARMAX-ORLANDO",
        person_identity_key="vecu_test123",
        authorized_by="system-integration",
        make_model="Honda Accord 2023"
    )

    assert authorization.authorization_id == "AUTH-12345678"
    assert authorization.vin == "9HGBH41JXMN999999"
    assert authorization.status == AuthorizationStatus.PENDING

unittest Example

import unittest
from unittest.mock import MagicMock
from vecu_custody import CustodyClient

class TestCustodyIntegration(unittest.TestCase):
    def setUp(self):
        self.client = MagicMock(spec=CustodyClient)

    def test_create_authorization(self):
        mock_auth = MagicMock()
        mock_auth.authorization_id = "AUTH-12345678"
        mock_auth.vin = "9HGBH41JXMN999999"

        self.client.authorizations.create.return_value = mock_auth

        authorization = self.client.authorizations.create(
            vin="9HGBH41JXMN999999",
            origin="LOC-AUCTION-MANHEIM-ATLANTA",
            destination="LOC-DEALERSHIP-CARMAX-ORLANDO",
            person_identity_key="vecu_test123",
            authorized_by="system-integration",
            make_model="Honda Accord 2023"
        )

        self.assertEqual(authorization.authorization_id, "AUTH-12345678")
        self.assertEqual(authorization.vin, "9HGBH41JXMN999999")

Testing with pytest

Fixtures

import pytest
from unittest.mock import MagicMock
from vecu_custody import CustodyClient

@pytest.fixture
def custody_client():
    """Provide a mock custody client."""
    return MagicMock(spec=CustodyClient)

@pytest.fixture
def releasable_result():
    """Mock releasable result."""
    result = MagicMock()
    result.is_releasable = True
    result.blockers = []
    return result

@pytest.fixture
def blocked_result():
    """Mock blocked result."""
    result = MagicMock()
    result.is_releasable = False
    result.blockers = ["HOLD", "PAYMENT"]
    return result

def test_releasable_vehicle(custody_client, releasable_result):
    custody_client.releasability.check_vin.return_value = releasable_result

    result = custody_client.releasability.check_vin(vin="9HGBH41JXMN999999")
    assert result.is_releasable is True
    assert result.blockers == []

def test_blocked_vehicle(custody_client, blocked_result):
    custody_client.releasability.check_vin.return_value = blocked_result

    result = custody_client.releasability.check_vin(vin="9HGBH41JXMN999999")
    assert result.is_releasable is False
    assert len(result.blockers) == 2

Parametrized Tests

import pytest
from unittest.mock import MagicMock

@pytest.mark.parametrize("vin,expected_releasable", [
    ("9HGBH41JXMN999999", True),
    ("1HGCM82633A123456", True),
    ("BLOCKED_VIN", False),
])
def test_releasability_check(vin, expected_releasable):
    client = MagicMock()
    result = MagicMock()
    result.is_releasable = expected_releasable
    client.releasability.check_vin.return_value = result

    result = client.releasability.check_vin(vin=vin)
    assert result.is_releasable == expected_releasable

Testing Async Code

Async Fixtures

import pytest
from unittest.mock import AsyncMock
from vecu_custody.aio import AsyncCustodyClient

@pytest.fixture
async def async_client():
    """Provide an async mock client."""
    return AsyncMock(spec=AsyncCustodyClient)

@pytest.mark.asyncio
async def test_async_create_authorization(async_client):
    mock_auth = AsyncMock()
    mock_auth.authorization_id = "AUTH-12345678"
    mock_auth.vin = "9HGBH41JXMN999999"

    async_client.authorizations.create.return_value = mock_auth

    authorization = await async_client.authorizations.create(
        vin="9HGBH41JXMN999999",
        origin="LOC-AUCTION-MANHEIM-ATLANTA",
        destination="LOC-DEALERSHIP-CARMAX-ORLANDO",
        person_identity_key="vecu_test123",
        authorized_by="system-integration",
        make_model="Honda Accord 2023"
    )

    assert authorization.authorization_id == "AUTH-12345678"
    assert authorization.vin == "9HGBH41JXMN999999"

@pytest.mark.asyncio
async def test_concurrent_operations(async_client):
    import asyncio

    mock_result = AsyncMock()
    mock_result.is_releasable = True
    async_client.releasability.check_vin.return_value = mock_result

    vins = ["VIN1", "VIN2", "VIN3"]

    tasks = [
        async_client.releasability.check_vin(vin=vin)
        for vin in vins
    ]

    results = await asyncio.gather(*tasks)
    assert len(results) == 3

Mocking with unittest.mock

Patching the Client

from unittest.mock import patch, MagicMock
from vecu_custody import CustodyClient

def test_with_patched_client():
    with patch('vecu_custody.CustodyClient.sandbox') as mock_sandbox:
        mock_client = MagicMock()
        mock_auth = MagicMock()
        mock_auth.authorization_id = "AUTH-123"
        mock_client.authorizations.create.return_value = mock_auth
        mock_sandbox.return_value = mock_client

        # Your application code that uses the client
        client = CustodyClient.sandbox(token="test-token")
        authorization = client.authorizations.create(
            vin="9HGBH41JXMN999999",
            origin="LOC-AUCTION-MANHEIM-ATLANTA",
            destination="LOC-DEALERSHIP-CARMAX-ORLANDO",
            person_identity_key="vecu_test123",
            authorized_by="system-integration",
            make_model="Honda Accord 2023"
        )

        assert authorization.authorization_id == "AUTH-123"

Patching Specific Methods

from unittest.mock import patch, MagicMock
from vecu_custody import CustodyClient

def test_releasability_check():
    client = CustodyClient.sandbox(token="test-token")

    with patch.object(client.releasability, 'check_vin') as mock_check:
        mock_result = MagicMock()
        mock_result.is_releasable = False
        mock_result.blockers = ["HOLD"]
        mock_check.return_value = mock_result

        result = client.releasability.check_vin(vin="9HGBH41JXMN999999")

        assert result.is_releasable is False
        assert len(result.blockers) == 1

Integration Testing

Test Against Sandbox

import pytest
from vecu_custody import CustodyClient
import os

@pytest.fixture
def sandbox_client():
    """Provide a real client connected to sandbox."""
    if not os.getenv("VECU_API_TOKEN"):
        pytest.skip("Sandbox credentials not available")

    return CustodyClient.sandbox(token=os.environ["VECU_API_TOKEN"])

@pytest.mark.integration
def test_real_api_call(sandbox_client):
    """Integration test against sandbox environment."""
    # This makes a real API call
    health = sandbox_client.get_health()
    assert health.status == "healthy"

Running Integration Tests Separately

# Run only unit tests (with mocks)
pytest -m "not integration"

# Run only integration tests (real API calls)
pytest -m integration

# Run all tests
pytest

Test Helpers

Custom Assertions

from vecu_custody.models import AuthorizationStatus

def assert_valid_authorization(authorization):
    """Assert that an authorization has all required fields."""
    assert authorization.authorization_id is not None
    assert authorization.vin is not None
    assert authorization.status in [
        AuthorizationStatus.PENDING,
        AuthorizationStatus.RELEASABLE,
        AuthorizationStatus.CANCELLED,
        AuthorizationStatus.EXPIRED
    ]
    assert authorization.person_identity_key is not None

def test_authorization_creation():
    mock_auth = MagicMock()
    mock_auth.authorization_id = "AUTH-123"
    mock_auth.vin = "9HGBH41JXMN999999"
    mock_auth.status = AuthorizationStatus.PENDING
    mock_auth.person_identity_key = "vecu_test123"

    assert_valid_authorization(mock_auth)

Test Data Builders

from dataclasses import dataclass
from typing import Optional

@dataclass
class AuthorizationBuilder:
    vin: str = "9HGBH41JXMN999999"
    origin: str = "LOC-AUCTION-MANHEIM-ATLANTA"
    destination: str = "LOC-DEALERSHIP-CARMAX-ORLANDO"
    person_identity_key: str = "vecu_test123"
    authorized_by: str = "system-integration"
    make_model: str = "Honda Accord 2023"

    def build(self, client):
        return client.authorizations.create(
            vin=self.vin,
            origin=self.origin,
            destination=self.destination,
            person_identity_key=self.person_identity_key,
            authorized_by=self.authorized_by,
            make_model=self.make_model
        )

def test_with_builder():
    client = MagicMock()
    mock_auth = MagicMock()
    client.authorizations.create.return_value = mock_auth

    authorization = AuthorizationBuilder(vin="CUSTOM_VIN").build(client)

    client.authorizations.create.assert_called_once()
    args, kwargs = client.authorizations.create.call_args
    assert kwargs['vin'] == "CUSTOM_VIN"

Coverage

Measuring Test Coverage

# Install coverage
pip install pytest-cov

# Run tests with coverage
pytest --cov=my_app --cov-report=html

# View coverage report
open htmlcov/index.html

Coverage Configuration

# pytest.ini or setup.cfg
[tool:pytest]
addopts = --cov=my_app --cov-report=term-missing
testpaths = tests

[coverage:run]
source = my_app
omit =
    */tests/*
    */venv/*

Best Practices

1. Use Mocks for Unit Tests

# Good - fast, isolated unit test
def test_create_authorization():
    client = MagicMock(spec=CustodyClient)
    mock_auth = MagicMock()
    client.authorizations.create.return_value = mock_auth

    authorization = client.authorizations.create(...)
    assert authorization is not None

# Avoid - slow, requires API access
def test_create_authorization():
    client = CustodyClient.sandbox(token="...")  # Real API call
    authorization = client.authorizations.create(...)

2. Test Error Paths

from vecu_custody.exceptions import ValidationError

def test_error_handling():
    client = MagicMock()
    client.authorizations.create.side_effect = ValidationError("Invalid VIN")

    with pytest.raises(ValidationError):
        client.authorizations.create(vin="INVALID", ...)

3. Separate Unit and Integration Tests

# tests/unit/test_authorizations.py
from unittest.mock import MagicMock

def test_authorization_logic():
    client = MagicMock()
    # Test business logic

# tests/integration/test_api.py
import pytest
from vecu_custody import CustodyClient

@pytest.mark.integration
def test_real_api():
    client = CustodyClient.sandbox(token="...")
    # Test against real API

4. Use Fixtures for Common Setup

@pytest.fixture
def releasable_client():
    client = MagicMock()
    result = MagicMock()
    result.is_releasable = True
    client.releasability.check_vin.return_value = result
    return client

@pytest.fixture
def blocked_client():
    client = MagicMock()
    result = MagicMock()
    result.is_releasable = False
    result.blockers = ["HOLD"]
    client.releasability.check_vin.return_value = result
    return client

def test_releasable_flow(releasable_client):
    result = releasable_client.releasability.check_vin(vin="...")
    assert result.is_releasable is True

def test_blocked_flow(blocked_client):
    result = blocked_client.releasability.check_vin(vin="...")
    assert result.is_releasable is False

Next Steps