Skip to content

Mock API Pattern

The Mock API Pattern allows AccessALI to develop independently without waiting for external API availability.

Problem Statement

AccessALI integrates with multiple external systems:

  • SAP (contracts, buyer data)
  • Salesforce (appointments, CRM)
  • SpringCM (documents)
  • ELedger (payments)
  • Data Warehouse (construction progress)

Challenges:

  • External APIs may not be ready during development
  • Testing with real APIs is slow and expensive
  • Development blocked waiting for API access
  • Hard to reproduce edge cases

Solution: Adapter Pattern

// Environment variable controls behavior
USE_MOCK_SAP="true"  // Development
USE_MOCK_SAP="false" // Production

// Usage is identical - adapter handles switching
import { sapAdapter } from '@/lib/adapters/sap-adapter'
const contract = await sapAdapter.getContract(id)

Architecture

graph LR
    UC[Use Case] --> ADAPTER[Adapter]
    ADAPTER --> |USE_MOCK=true| MOCK[Mock Implementation]
    ADAPTER --> |USE_MOCK=false| REAL[Real API Client]

    MOCK --> DB[(Mock Data<br/>in Database)]
    REAL --> EXT[External API]

Implementation

1. Base Adapter

// src/lib/adapters/base-adapter.ts
export function createAdapter<T>(
  MockClass: new () => T,
  RealClass: new () => T | null,
  envVar: string
): T {
  const useMock = process.env[envVar] === 'true'

  if (useMock) {
    return new MockClass()
  }

  if (!RealClass) {
    throw new Error(`Real implementation not available for ${envVar}`)
  }

  return new RealClass()
}

2. SAP Adapter

// src/lib/adapters/sap-adapter.ts
import { createAdapter } from './base-adapter'
import { SAPMock } from '@/lib/mocks/sap-mock'
import { SAPClient } from '@/lib/integrations/sap-client'

export const sapAdapter = createAdapter(
  SAPMock,
  SAPClient,
  'USE_MOCK_SAP'
)

3. Mock Implementation

// src/lib/mocks/sap-mock.ts
import { prisma } from '@/lib/prisma'

export class SAPMock {
  async getContract(contractId: string) {
    const mock = await prisma.mockSAPContract.findUnique({
      where: { contractId }
    })

    if (!mock) {
      throw new Error(`Contract ${contractId} not found`)
    }

    return {
      contractId: mock.contractId,
      buyerId: mock.buyerId,
      propertyCode: mock.propertyCode,
      totalAmount: mock.totalAmount,
      // ... map all fields
    }
  }

  async getBuyerProfile(buyerId: string) {
    const mock = await prisma.mockSAPBuyer.findUnique({
      where: { buyerId }
    })

    return {
      buyerId: mock.buyerId,
      firstName: mock.firstName,
      lastName: mock.lastName,
      email: mock.email,
      // ... map all fields
    }
  }
}

4. Real Implementation

// src/lib/integrations/sap-client.ts
export class SAPClient {
  private baseUrl = process.env.SAP_API_URL
  private apiKey = process.env.SAP_API_KEY

  async getContract(contractId: string) {
    const response = await fetch(
      `${this.baseUrl}/contracts/${contractId}`,
      {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        }
      }
    )

    if (!response.ok) {
      throw new Error(`SAP API error: ${response.statusText}`)
    }

    return response.json()
  }

  async getBuyerProfile(buyerId: string) {
    const response = await fetch(
      `${this.baseUrl}/buyers/${buyerId}`,
      {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        }
      }
    )

    return response.json()
  }
}

Usage in Use Cases

// src/lib/use-cases/dashboard.ts
import { sapAdapter } from '@/lib/adapters/sap-adapter'
import { getUserProperties } from '@/lib/repositories/property-repository'

export async function getDashboardDataUseCase(userId: string) {
  const properties = await getUserProperties(userId)

  // Works with both mock and real SAP
  const contracts = await Promise.all(
    properties.map(p => sapAdapter.getContract(p.sapContractId))
  )

  return {
    properties,
    contracts
  }
}

Migration Path

Phase 1: Mock Only (Weeks 1-16)

USE_MOCK_SAP="true"
USE_MOCK_SALESFORCE="true"
USE_MOCK_SPRINGCM="true"
USE_MOCK_ELEDGER="true"

Focus: Build all features with predictable mock data

Phase 2: Real API Integration (Weeks 17-24)

# Switch one API at a time
USE_MOCK_SAP="false"  # Real SAP API
USE_MOCK_SALESFORCE="true"
USE_MOCK_SPRINGCM="true"
USE_MOCK_ELEDGER="true"

# Configure real API credentials
SAP_API_URL="https://sap.ayalaland.com.ph/api"
SAP_API_KEY="production-key"

Phase 3: Production (Week 25+)

# All real APIs
USE_MOCK_SAP="false"
USE_MOCK_SALESFORCE="false"
USE_MOCK_SPRINGCM="false"
USE_MOCK_ELEDGER="false"

Benefits

  1. Independent Development

    • Don't wait for external APIs
    • Develop at your own pace
    • No external dependencies
  2. Predictable Testing

    • Consistent test data
    • Reproducible results
    • Easy edge case testing
  3. Fast Iteration

    • No network latency
    • Instant responses
    • Local development
  4. Seamless Migration

    • No code changes
    • Environment variable switch
    • Gradual rollout
  5. Resilience

    • Fallback to mocks if API fails
    • Graceful degradation
    • Always functional

Mock Data Management

Database Schema

// prisma/schema.prisma
model MockSAPContract {
  contractId    String   @id
  buyerId       String
  propertyCode  String
  totalAmount   Decimal
  status        String
  createdAt     DateTime @default(now())
}

model MockSAPBuyer {
  buyerId    String @id
  firstName  String
  lastName   String
  email      String
  phone      String
}

Seeding Mock Data

// prisma/seed.ts
await prisma.mockSAPContract.createMany({
  data: [
    {
      contractId: 'SAP-001',
      buyerId: 'BUYER-001',
      propertyCode: 'ALV-T1-101',
      totalAmount: 5000000,
      status: 'ACTIVE'
    },
    // ... more mock contracts
  ]
})

Best Practices

Do's

  • ✅ Mirror real API response structure
  • ✅ Include realistic data
  • ✅ Store mocks in database
  • ✅ Version mock data
  • ✅ Document API contracts

Don'ts

  • ❌ Hardcode mock responses
  • ❌ Use different structure than real API
  • ❌ Skip error scenarios
  • ❌ Forget to update mocks when API changes

Testing with Mocks

// tests/use-cases/dashboard.test.ts
describe('getDashboardDataUseCase', () => {
  beforeEach(() => {
    process.env.USE_MOCK_SAP = 'true'
  })

  it('fetches dashboard data', async () => {
    const data = await getDashboardDataUseCase('user-123')

    expect(data.contracts).toHaveLength(2)
    expect(data.contracts[0].contractId).toBe('SAP-001')
  })
})

Learn More