Careeroza
Real World Example
Testing a Wallet Top-up Service (Paytm / PhonePe style)
The service below adds money to a wallet after the bank confirms the payment. The tests cover the happy path, a declined payment and a duplicate request — the cases that cost real money if they break.
@Service
public class TopUpService {
private final WalletRepository wallets;
private final BankClient bank;
private final NotificationService notifications;
public TopUpService(WalletRepository wallets, BankClient bank, NotificationService notifications) {
this.wallets = wallets; this.bank = bank; this.notifications = notifications;
}
@Transactional
public TopUpResult topUp(String userId, String requestId, BigDecimal amount) {
if (wallets.existsTopUp(requestId)) return TopUpResult.DUPLICATE; // idempotency
if (bank.debit(userId, amount) != BankStatus.APPROVED) return TopUpResult.DECLINED;
Wallet w = wallets.findByUserId(userId).orElseThrow();
w.credit(amount);
wallets.save(w);
wallets.recordTopUp(requestId);
notifications.push(userId, "Rs " + amount + " added to your wallet");
return TopUpResult.SUCCESS;
}
}The Tests
@ExtendWith(MockitoExtension.class)
class TopUpServiceTest {
@Mock WalletRepository wallets;
@Mock BankClient bank;
@Mock NotificationService notifications;
@InjectMocks TopUpService service;
@Test
void creditsWalletWhenBankApproves() {
Wallet wallet = new Wallet("u1", new BigDecimal("100"));
when(wallets.existsTopUp("REQ-1")).thenReturn(false);
when(bank.debit("u1", new BigDecimal("500"))).thenReturn(BankStatus.APPROVED);
when(wallets.findByUserId("u1")).thenReturn(Optional.of(wallet));
TopUpResult result = service.topUp("u1", "REQ-1", new BigDecimal("500"));
assertEquals(TopUpResult.SUCCESS, result);
assertEquals(new BigDecimal("600"), wallet.balance());
verify(wallets).recordTopUp("REQ-1");
verify(notifications).push("u1", "Rs 500 added to your wallet");
}
@Test
void doesNotCreditWhenBankDeclines() {
when(wallets.existsTopUp("REQ-2")).thenReturn(false);
when(bank.debit(any(), any())).thenReturn(BankStatus.DECLINED);
assertEquals(TopUpResult.DECLINED, service.topUp("u1", "REQ-2", BigDecimal.TEN));
verify(wallets, never()).save(any());
verifyNoInteractions(notifications);
}
@Test
void duplicateRequestIsIgnored() {
when(wallets.existsTopUp("REQ-1")).thenReturn(true);
assertEquals(TopUpResult.DUPLICATE, service.topUp("u1", "REQ-1", BigDecimal.TEN));
verifyNoInteractions(bank, notifications); // the user is not charged twice
}
}Example
mvn testExample output
[INFO] Running com.wallet.TopUpServiceTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.612 s [INFO] [INFO] Results: [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESS
Test Pyramid
▲ E2E / UI tests (few, slow) Selenium, Playwright
▲▲▲ Integration tests @SpringBootTest, @DataJpaTest, Testcontainers
▲▲▲▲▲▲ Unit tests (many, fast) JUnit 5 + Mockito ← this module