Skip to content

Testing strategy

Layer Tooling What it proves
Unit JUnit 5, AssertJ Pure logic: router, policy tables, state transitions, deadline arithmetic
Contract (consumer) WireMock container + stubs/wallet-wiremock/mappings Our wallet client handles every documented wallet behaviour
Integration @SpringBootTest + Testcontainers (Postgres 17, WireMock) End-to-end payment flows, DB state, outbox, workers
Failure modes WireMock delays, faults and scenarios The fault matrix below
Load smoke k6 (tools/load/, M2-T5) The SLI holds and pools stay bounded when the wallet is slow

Container tests use @Testcontainers(disabledWithoutDocker = true): they skip locally without Docker, and they always run in CI.

Wallet stub: behaviour by account prefix

The same mappings serve docker compose and the tests. See stubs/wallet-wiremock.

Account v1 POST /debits v1 lookup v2 POST /holds
ACC-OK-* 201 [] 201 HELD
ACC-NSF-* 409 INSUFFICIENT_FUNDS [] 409
ACC-DUP-* 409 DUPLICATE_REFERENCE + existingDebitId [] —
ACC-BLOCKED-* 423 [] —
ACC-DOWN-* 503 (credits too) [] 503
ACC-SLOW-* 201 after 3 s (lands late) found 201 after 3 s, lookup found
ACC-LOST-* 504 after 3 s (never lands) [] —
anything else 404 ACCOUNT_NOT_FOUND [] —

Fault matrix

# Injected condition Assertion Task
F1 Wallet answers 201 after 3 s Till gets DECLINED WALLET_TIMEOUT in < 2.4 s, or APPROVED if the lookup finds it. The pool never saturates. M1-T4
F2 Wallet 503 Breaker opens after the minimum number of calls. Declines take milliseconds. Nothing is queued. M1-T3
F3 Wallet flaps 50/50 Open duration backs off. No synchronised probe storm. M2-T5
F4 Debit lands, response lost (SLOW) Exactly one debit and exactly one compensating credit M1-T7
F5 Debit never lands (LOST) REVERSAL_PENDING closes as REVERSED with no credit M1-T7
F6 Same key, same body Stored response replayed. One debit. M1-T1, M1-T4
F7 Same key, different body 422. No execution. M1-T1
F8 Same key, concurrent One executes, the other gets 409 M1-T1
F9 Two concurrent payments, one account, funds for one Exactly one APPROVED. This is the race regression, and it needs no fault injection. M2-T5
F10 Reversal before original Late original declined REVERSED_BEFORE_AUTHORISATION M1-T5
F11 Capture response lost Exactly one capture M3-T3
F12 Kill switch mid-flight In-flight holds complete. New payments use DIRECT_DEBIT. M3-T5
F13 Our own DB down 503 problem response, not a hang (Hikari 250 ms) M2-T5