Cosmic Module

O

Qubits of DPK

March 20, 2026

Core Open Source

The Analogy

Integration tests are like a quality inspector at a car factory. Unit tests check individual parts ("does this engine bolt work?"). Integration tests test the whole car driving on a real road ("does the entire system work together?").
Fineract's integration tests start a REAL server, make REAL HTTP calls, hit a REAL database, and verify REAL responses.

Test Infrastructure Overview

javascript
QUBITS OF DPK
1integration-tests/
2├── src/test/java/org/apache/fineract/integrationtests/
3│   ├── common/Helper classes (shared by all tests)
4│   │   ├── ClientHelper.javaClient API calls
5│   │   ├── LoanTransactionHelper.javaLoan API calls
6│   │   ├── SchedulerJobHelper.javaScheduler job calls
7│   │   ├── OfficeHelper.javaOffice API calls
8│   │   └── Utils.javaBase URL, auth, date utils
9│   ├── support/
10│   │   └── instancemode/
11│   │       ├── ConfigureInstanceMode.java      ← annotation
12│   │       └── InstanceModeSupportExtension.javaJUnit extension
13│   ├── ClientExternalIdTest.javaYour PR #5659
14│   ├── InstanceModeIntegrationTest.javaYour PR #5658
15│   └── ...200+ test files
16└── src/test/resources/
17    └── fineract-test.propertiesTest server URL, credentials

How Integration Tests Connect to the Server

java
QUBITS OF DPK
1// Utils.java configures the base URL
2public static void initializeRESTAssured() {
3    RestAssured.baseURI = "https://localhost";
4    RestAssured.port = 8443;
5    RestAssured.basePath = "/fineract-provider/api/v1";
6    RestAssured.useRelaxedHTTPSValidation();  // skip SSL check
7}
Tests expect a running Fineract server at https://localhost:8443. That's why you ran docker-compose up before running tests.

The Two Client Approaches — Old vs New

OLD WAY — RestAssured (being migrated away from)

java
QUBITS OF DPK
1// 1. Setup (boilerplate in EVERY test's @BeforeEach)
2Utils.initializeRESTAssured();
3RequestSpecification requestSpec = new RequestSpecBuilder()
4    .setContentType(ContentType.JSON)
5    .build();
6requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
7requestSpec.header("Fineract-Platform-TenantId", "default");
8ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
9
10// 2. Make API call (verbose)
11Map<String, Object> map = new HashMap<>();
12map.put("officeId", "1");
13map.put("firstname", "John");
14String json = new Gson().toJson(map);
15String response = Utils.performServerPost(requestSpec, responseSpec, "/clients", json);
16PostClientsResponse result = GSON.fromJson(response, PostClientsResponse.class);
17
18// 3. Test failure expected (status 405)
19ClientHelper.createClient(requestSpec, responseSpec405, request); // no exception even on failure

NEW WAY — fineract-client-feign (what your PRs introduce)

java
QUBITS OF DPK
1// 1. No setup needed. FineractClientHelper auto-configures from test.properties
2
3// 2. Make API call (clean)
4PostClientsResponse result = ClientHelper.createClient(request);
5// That's it. Authentication, URL, serialization — all handled.
6
7// 3. Test failure expected
8assertThrows(CallFailedRuntimeException.class, () -> ClientHelper.createClient(request));
9// Feign throws automatically on non-2xx

The Feign Client Stack

javascript
QUBITS OF DPK
1Test Code
2    |
34ClientHelper.createClient(request)
5    |
67Calls.ok( FineractClientHelper.getFineractClient().clients.createClient(request) )
8    |         |                    |                |
9    |         |                    |                └─ ClientApi (generated from OpenAPI)
10    |         |                    └─ FineractClient (has all API groups as fields)
11    |         └─ Returns pre-configured FineractClient (OkHttp + Retrofit under hood)
12    └─ Executes the Call<T>, checks 2xx, returns body, throws CallFailedRuntimeException on error

FineractClientHelper

java
QUBITS OF DPK
1public class FineractClientHelper {
2    private static FineractClient fineractClient;
3
4    public static FineractClient getFineractClient() {
5        if (fineractClient == null) {
6            fineractClient = FineractClient.builder()
7                .baseUrl("https://localhost:8443/fineract-provider/api/v1/")
8                .basicAuth("mifos", "password")
9                .tenantId("default")
10                .build();
11        }
12        return fineractClient;
13    }
14}

Calls.ok()

java
QUBITS OF DPK
1public static <T> T ok(Call<T> call) {
2    Response<T> response = call.execute();
3    if (!response.isSuccessful()) {
4        throw new CallFailedRuntimeException(call, response);
5    }
6    return response.body();
7}

The Generated fineract-client

The fineract-client module is auto-generated from the OpenAPI spec during build.
javascript
QUBITS OF DPK
1// build.gradle
2openApiGenerate {
3    generatorName = "java"
4    inputSpec = "$rootDir/fineract-provider/src/main/resources/openapi.json"
5    outputDir = "$buildDir/generated/java"
6}
This generates:
  • ClientApi.java — all /clients endpoints
  • JobsApi.java — all /jobs endpoints
  • LoanApi.java — all /loans endpoints
  • All request/response model classes (PostClientsRequest, GetClientsClientIdResponse etc.)
You NEVER edit these files manually. They are regenerated every build.

Writing a New Integration Test (The Right Way)

java
QUBITS OF DPK
1public class MyNewTest {
2
3    // No requestSpec/responseSpec needed!
4
5    @Test
6    public void testCreateAndRetrieveClient() {
7        // 1. Create using feign
8        PostClientsRequest request = ClientHelper.defaultClientCreationRequest();
9        PostClientsResponse created = ClientHelper.createClient(request);
10        assertNotNull(created.getClientId());
11
12        // 2. Retrieve using feign
13        GetClientsClientIdResponse client = Calls.ok(
14            FineractClientHelper.getFineractClient().clients
15                .retrieveOne(created.getClientId(), false, null)
16        );
17        assertEquals(request.getFirstname(), client.getFirstname());
18
19        // 3. Test failure case
20        assertThrows(CallFailedRuntimeException.class, () -> {
21            // Try to get a client that doesn't exist
22            Calls.ok(FineractClientHelper.getFineractClient().clients
23                .retrieveOne(999999L, false, null));
24        });
25    }
26}

@ConfigureInstanceMode — JUnit Extension (Your PR #5658)

This is an elegant JUnit 5 pattern:
java
QUBITS OF DPK
1// Custom annotation
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.METHOD)
4public @interface ConfigureInstanceMode {
5    boolean readEnabled();
6    boolean writeEnabled();
7    boolean batchWorkerEnabled();
8    boolean batchManagerEnabled();
9}
10
11// JUnit Extension that reads the annotation and configures Fineract
12@ExtendWith(InstanceModeSupportExtension.class)
13public class InstanceModeIntegrationTest {
14
15    @ConfigureInstanceMode(readEnabled = true, writeEnabled = false, ...)
16    @Test
17    public void testReadOnlyMode() {
18        // InstanceModeSupportExtension automatically:
19        // 1. Reads the annotation
20        // 2. Calls PUT /instancemode to set read-only before the test
21        // 3. Runs the test
22        // 4. Restores normal mode after the test
23    }
24}

Test Helper Pattern — Why Helpers Exist

Instead of every test writing Calls.ok(getFineractClient().clients.createClient(...)), we wrap it in ClientHelper.createClient(). This:
  • Reduces code duplication
  • Makes tests readable
  • When API changes, only update the helper
The migration FINERACT-2441 is converting helpers from RestAssured to feign.

Mentor Cheat Sheet