Automatic Testing
This guide covers automated testing of the mortgage system, focusing on the mocking system for controlling integration responses and test personas for consistent, repeatable test scenarios.
Overview
Building a testing rig follows the same patterns as building a channel (like the customer-facing frontend). You interact with the API to start processes, poll for tasks, and complete them. The Frontend documentation covers these patterns in detail.
For testing, you add:
- Mocking system - Control third-party integration responses
- Test personas - Pre-defined individuals with aligned mock data
Writing Tests
Automated tests verify that the process behaves correctly given specific inputs. The goal is to ensure that:
- Different personas produce different outcomes - An applicant with good credit should reach an offer, while one with poor credit should be declined
- Tasks appear in the correct order - The flow progresses through expected steps
- Task completion advances the process - Completing a task triggers the next step
- Edge cases are handled - Unusual data combinations don't break the flow
A typical test starts a process with specific inputs, progresses through tasks, and asserts that the flow arrives at expected states.
The code examples below are pseudo code for illustration purposes. Endpoint paths and request/response structures may vary depending on your solution configuration.
Basic Test Structure
test('applicant with good credit reaches offer task', async () => {
// Start process with a persona
const { flowId } = await startProcess({
persona: goodCreditPersona,
purpose: 'refinance'
})
// Complete tasks until we reach the expected state
await completeTasksUntil(flowId, 'offer-task')
// Assert we arrived at the expected task
const tasks = await getPendingTasks(flowId)
expect(tasks[0].taskType).toBe('offer-task')
})
Testing Different Paths
test('applicant with poor credit is declined', async () => {
const { flowId } = await startProcess({
persona: poorCreditPersona,
purpose: 'refinance'
})
await completeTasksUntil(flowId, 'decline-task')
const tasks = await getPendingTasks(flowId)
expect(tasks[0].taskType).toBe('decline-task')
})
Testing Task Completion
test('completing income verification progresses to next task', async () => {
const { flowId } = await startProcess({ persona: testPersona })
// Wait for specific task
const incomeTask = await waitForTask(flowId, 'verify-income')
// Complete with specific input
await completeTask(incomeTask.taskId, {
decision: 'approved'
})
// Assert next task appears
const nextTask = await waitForTask(flowId, 'verify-assets')
expect(nextTask).toBeDefined()
})
Helper Functions
async function startProcess({ persona, purpose }) {
return fetch('/api/flow-definitions/mortgage', {
method: 'POST',
body: JSON.stringify({
purpose,
households: [{
stakeholders: [{
nationalId: persona.nationalId,
role: 'applicant'
}]
}]
})
}).then(r => r.json())
}
async function waitForTask(flowId, taskType) {
while (true) {
const tasks = await getPendingTasks(flowId)
const task = tasks.find(t => t.taskType === taskType)
if (task) return task
await sleep(1000)
}
}
async function completeTasksUntil(flowId, targetTaskType) {
while (true) {
const tasks = await getPendingTasks(flowId)
if (tasks[0]?.taskType === targetTaskType) return
if (tasks[0]) {
await completeTask(tasks[0].taskId, {})
}
await sleep(1000)
}
}
Mocking System
The mocking system controls responses from third-party integrations, allowing tests to run without hitting real external services.
How It Works
Each integration has mock files containing request/response pairs. When a request is made, the system matches against identifiers in the request to find the appropriate mock response.
{
"/endpoint": {
"POST": [
{
"search": { "identifier": "value-1" },
"responseBody": { "name": "Person One" }
},
{
"search": { "identifier": "value-2" },
"responseBody": { "name": "Person Two" }
}
]
}
}
Matching Identifiers
Integrations use identifiers from the request to match against mock entries. The specific identifier fields vary by integration type and solution configuration.
Mix and Match
The mocking system allows mixing mocked and real requests within the same process instance. If a matching mock entry exists for an identifier, the mock is used. Otherwise, the real integration is called.
This enables:
- Testing specific integration failures
- Creating edge-case data scenarios
- Running partial integration tests
Test Personas
Test personas are pre-defined individuals with aligned mock data across all integrations.
Why Personas?
- Consistency - All mocks return coherent data (income matches tax records, properties match registry)
- Repeatability - Same persona always produces same results
- Scenario coverage - Different personas test different paths (high/low income, good/poor credit)
Data Alignment
When you start a process with a persona's identifier, all integration lookups return that persona's aligned data:
- Party lookup returns their personal information
- Income service returns their registered income
- Credit scoring returns their credit grade
- Property registry returns their property details
- Debt registry returns their existing obligations
Available Personas
See the Manual Testing page for the complete list of available personas for your solution.