FINERACT-1724: Document integration and e2e testing in fineract

This commit is contained in:
Attila Budai
2025-10-01 12:14:57 +02:00
committed by Adam Saghy
parent 5a56ea50f3
commit cb7ead208f
2 changed files with 1355 additions and 3 deletions
@@ -1,6 +1,556 @@
[[testing-cucumber]]
= Cucumber
= Cucumber E2E Tests
TBD
Apache Fineract's E2E test suite provides comprehensive coverage of business functionality using Cucumber BDD (Behavior-Driven Development) framework. These tests serve as both functional validation and living documentation of the system's capabilities.
== Overview
=== Architecture
* *fineract-e2e-tests-runner*: Contains all Cucumber feature files and test scenarios
* *fineract-e2e-tests-core*: Contains step definitions, test utilities, and supporting code
* *Framework*: Cucumber with Java, using Gherkin syntax for readable test specifications
* *Prerequisites*: Running Apache Fineract instance (typically on port 8443 with HTTPS)
=== Test Organization
* Feature files located in: `fineract-e2e-tests-runner/src/test/resources/features/`
* Step definitions in: `fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/`
* Tests are tagged with TestRail IDs for traceability (e.g., `@TestRailId:C16`)
* Special tags include `@Smoke` for quick validation tests
== Prerequisites
=== Required Software
* *Java 21*: Apache Fineract requires Java 21 (Azul Zulu JDK recommended)
* *Database*: MariaDB 11.5.2, PostgreSQL 17.4, or MySQL 9.1
* *Git*: For source code management
* *Gradle 8.14.3*: Included via wrapper
=== Database Setup
Before running E2E tests, ensure the databases are created:
[source,bash]
----
# Create required databases
./gradlew createDB -PdbName=fineract_tenants
./gradlew createDB -PdbName=fineract_default
----
== Configuration
=== Connection Configuration
E2E tests connect to the running Fineract instance. Default configuration in `fineract-e2e-tests-core/src/test/resources/fineract-test-application.properties`:
[source,properties]
----
# Connection details to running backend
fineract-test.api.base-url=${BASE_URL:https://localhost:8443}
fineract-test.api.username=${TEST_USERNAME:mifos}
fineract-test.api.password=${TEST_PASSWORD:password}
fineract-test.api.tenant-id=${TEST_TENANT_ID:default}
----
To override defaults, use environment variables:
[source,bash]
----
export BASE_URL=http://localhost:8080
export TEST_USERNAME=mifos
export TEST_PASSWORD=password
export TEST_TENANT_ID=default
----
=== Test Data Initialization
IMPORTANT: Many E2E tests require pre-configured test data (loan products, charges, configurations). This initialization is controlled by the `fineract-test.initialization.enabled` property.
*Default Behavior*:
* By default, initialization is *DISABLED* (`fineract-test.initialization.enabled=false`)
* Without initialization, tests will fail with errors like:
[source]
----
java.lang.IllegalArgumentException: Loan product [LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL] not found
----
*How to Enable Initialization*:
Method 1 - Environment Variable (Recommended):
[source,bash]
----
cd fineract-e2e-tests-runner
INITIALIZATION_ENABLED=true ../gradlew cucumber
----
Method 2 - System Property:
[source,bash]
----
cd fineract-e2e-tests-runner
../gradlew cucumber -DINITIALIZATION_ENABLED=true
----
Method 3 - Modify Properties File:
Edit `fineract-e2e-tests-core/src/test/resources/fineract-test-application.properties`:
[source,properties]
----
fineract-test.initialization.enabled=true
----
*What Initialization Creates*:
* 100+ loan products with specific configurations
* Various charge types (NSF fees, processing fees, etc.)
* Payment allocation rules
* Interest calculation configurations
* Advanced payment allocation strategies
* Progressive loan schedule configurations
*When to Use Initialization*:
* First-time test execution on a fresh database
* After database reset/recreation
* When running tests that require specific loan products
* Testing new features that depend on pre-configured products
NOTE: Initialization takes additional time (2-5 minutes) as it creates extensive test data. Consider running it once and reusing the database for multiple test runs.
=== Business Date Configuration
CRITICAL: The Business Date feature must be enabled in the database for many E2E tests to function correctly.
*Default Behavior*:
* Business Date is *DISABLED* by default in fresh Fineract installations
* Without Business Date enabled, tests fail with:
[source,json]
----
{"errors":[{
"defaultUserMessage":"Business date functionality is not enabled",
"developerMessage":"Business date functionality is not enabled",
"userMessageGlobalisationCode":"business.date.is.not.enabled"
}]}
----
*How to Enable Business Date*:
Method 1 - Via SQL (Direct Database):
[source,bash]
----
mysql -u root -pmysql fineract_default -e \
"UPDATE c_configuration SET enabled = 1 WHERE name = 'enable-business-date';"
----
Method 2 - Via API (After Fineract is Running):
[source,bash]
----
curl -X PUT https://localhost:8443/fineract-provider/api/v1/configurations/name/enable-business-date \
-H "Authorization: Basic bWlmb3M6cGFzc3dvcmQ=" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'
----
*Verification*:
[source,bash]
----
mysql -u root -pmysql fineract_default -e \
"SELECT * FROM c_configuration WHERE name LIKE '%business%';"
----
== Running E2E Tests
=== Complete Workflow
==== Step 1: Start Fineract
[source,bash]
----
# Start Fineract in background
./gradlew bootRun
----
Wait for Fineract to be fully started. You can verify by checking:
[source,bash]
----
curl -k https://localhost:8443/actuator/health
----
==== Step 2: Enable Business Date (if needed)
[source,bash]
----
mysql -u root -pmysql fineract_default -e \
"UPDATE c_configuration SET enabled = 1 WHERE name = 'enable-business-date';"
----
==== Step 3: Run E2E Tests
Navigate to the E2E tests module:
[source,bash]
----
cd fineract-e2e-tests-runner
----
*Run All E2E Tests*:
[source,bash]
----
# First run with initialization
INITIALIZATION_ENABLED=true ../gradlew cucumber
# Subsequent runs without initialization (faster)
../gradlew cucumber
----
*Run Specific Feature File*:
[source,bash]
----
../gradlew cucumber -Pcucumber.features="src/test/resources/features/Loan.feature"
----
*Run Tests by Tag*:
[source,bash]
----
# Run only smoke tests
../gradlew cucumber -Pcucumber.tags="@Smoke"
# Run specific TestRail test
../gradlew cucumber -Pcucumber.tags="@TestRailId:C16"
# Run multiple tags
../gradlew cucumber -Pcucumber.tags="@Smoke and @TestRailId:C16"
----
*Run Tests with Custom Configuration*:
[source,bash]
----
BASE_URL=http://localhost:8080 \
TEST_USERNAME=admin \
TEST_PASSWORD=admin123 \
INITIALIZATION_ENABLED=true \
../gradlew cucumber
----
=== Gradle Command Options
==== Basic Cucumber Task
[source,bash]
----
../gradlew cucumber
----
==== Feature File Selection
[source,bash]
----
# Single feature
../gradlew cucumber -Pcucumber.features="src/test/resources/features/Client.feature"
# Multiple features
../gradlew cucumber -Pcucumber.features="src/test/resources/features/Client.feature:src/test/resources/features/Loan.feature"
# Specific scenario by line number
../gradlew cucumber -Pcucumber.features="src/test/resources/features/Loan.feature:45"
----
==== Tag-Based Execution
[source,bash]
----
# Single tag
../gradlew cucumber -Pcucumber.tags="@Smoke"
# Multiple tags (AND)
../gradlew cucumber -Pcucumber.tags="@Smoke and @TestRailId:C16"
# Multiple tags (OR)
../gradlew cucumber -Pcucumber.tags="@Smoke or @TestRailId:C16"
# Exclude tags
../gradlew cucumber -Pcucumber.tags="not @ignore"
# Complex tag expression
../gradlew cucumber -Pcucumber.tags="@Smoke and not @ignore"
----
==== Report Generation
[source,bash]
----
# Generate HTML report
../gradlew cucumber -Dcucumber.plugin="pretty,html:build/cucumber-reports/cucumber.html"
# Generate JSON report
../gradlew cucumber -Dcucumber.plugin="json:build/cucumber-reports/cucumber.json"
# Multiple report formats
../gradlew cucumber -Dcucumber.plugin="pretty,html:build/cucumber-reports/cucumber.html,json:build/cucumber-reports/cucumber.json"
# Generate Allure report (comprehensive visual reporting)
../gradlew cucumber allureReport
----
After running tests with Allure, the report is available at:
[source]
----
fineract-e2e-tests-runner/build/reports/allure-report/index.html
----
NOTE: Allure provides rich visual reports with test history, statistics, and detailed execution information. Open the `index.html` file in a browser to view the interactive report.
==== Clean and Run
[source,bash]
----
# Clean previous test results and run
../gradlew clean cucumber
----
=== Advanced Execution Scenarios
==== Running Against Different Environment
[source,bash]
----
# Against staging environment
BASE_URL=https://staging.example.com:8443 \
TEST_USERNAME=staging_user \
TEST_PASSWORD=staging_pass \
../gradlew cucumber
----
==== Running with External Event Verification
[source,bash]
----
# Enable external event verification (requires ActiveMQ)
ACTIVEMQ_BROKER_URL=tcp://localhost:61616 \
ACTIVEMQ_BROKER_USERNAME=admin \
ACTIVEMQ_BROKER_PASSWORD=admin \
ACTIVEMQ_TOPIC_NAME=fineract-events \
EVENT_VERIFICATION_ENABLED=true \
../gradlew cucumber
----
==== Running with TestRail Integration
[source,bash]
----
TESTRAIL_ENABLED=true \
TESTRAIL_BASEURL=https://testrail.example.com \
TESTRAIL_USERNAME=test@example.com \
TESTRAIL_PASSWORD=testrail_password \
TESTRAIL_RUN_ID=123 \
../gradlew cucumber
----
== Test Development
=== Feature Coverage
The E2E tests cover the following functional domains:
==== Client Management
* Client creation and management
* Address management
* Document management
* Family member tracking
==== Loan Management
* Loan application and approval
* Disbursement
* Repayment processing
* Charges and fees
* Advanced features: chargeback, charge-off, re-aging, re-amortization
* Specialized loans: down payment, merchant-issued refund
==== Savings Account Management
* Account opening and activation
* Deposits and withdrawals
* Interest calculation
* Account closure
==== Accounting
* Journal entry validation
* Asset externalization
* GL account mapping
==== Operational Processes
* Close of Business (COB)
* Inline COB
* Business date management
* Batch API operations
=== Writing New E2E Tests
When writing new Cucumber tests:
1. *Create Feature File*: Add new `.feature` file in `fineract-e2e-tests-runner/src/test/resources/features/`
2. *Use Gherkin Syntax*:
[source,gherkin]
----
Feature: Loan Disbursement
Scenario: Successful loan disbursement
Given A client named "John Doe"
When Admin creates a loan for client with amount "1000"
And Admin approves the loan
And Admin disburses the loan on business date
Then Loan status is "Active"
And Loan outstanding balance is "1000"
----
3. *Implement Step Definitions*: Add corresponding step definitions in `fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/`
4. *Add Tags*: Tag scenarios appropriately:
[source,gherkin]
----
@TestRailId:C1234 @Smoke
Scenario: Critical loan test
----
5. *Verify with Gradle*:
[source,bash]
----
cd fineract-e2e-tests-runner
../gradlew cucumber -Pcucumber.features="src/test/resources/features/YourNewFeature.feature"
----
== Troubleshooting
=== Common Test Failures
==== Connection Issues
*Symptom*: Tests fail with connection refused errors
*Solutions*:
[source,bash]
----
# Verify Fineract is running
curl -k https://localhost:8443/actuator/health
# Check if port is in use
netstat -tulpn | grep 8443
# Check logs (logs go to console/stdout)
# If running in background, redirect output:
# ./gradlew bootRun > build/bootRun.log 2>&1 &
# tail -f build/bootRun.log
----
==== Business Date Issues
*Symptom*: Tests fail with "Business date functionality is not enabled"
*Solutions*:
[source,bash]
----
# Enable Business Date
mysql -u root -pmysql fineract_default -e \
"UPDATE c_configuration SET enabled = 1 WHERE name = 'enable-business-date';"
# Verify
mysql -u root -pmysql fineract_default -e \
"SELECT * FROM c_configuration WHERE name = 'enable-business-date';"
----
==== Data Dependencies
*Symptom*: Tests fail due to missing products or charges
*Solutions*:
[source,bash]
----
# Run with initialization enabled
cd fineract-e2e-tests-runner
INITIALIZATION_ENABLED=true ../gradlew cucumber
----
==== Authentication Failures
*Symptom*: 401 or 403 errors
*Solutions*:
[source,bash]
----
# Verify credentials
TEST_USERNAME=mifos TEST_PASSWORD=password ../gradlew cucumber
# Check user permissions in database
mysql -u root -pmysql fineract_default -e \
"SELECT * FROM m_appuser WHERE username = 'mifos';"
----
=== Debugging Tips
==== Enable Detailed Logging
[source,bash]
----
# Run with verbose output
../gradlew cucumber -Dcucumber.plugin="pretty" --info
# Save output to file
../gradlew cucumber > test-output.log 2>&1
----
==== Check Database State
[source,bash]
----
# Check loan products after initialization
mysql -u root -pmysql fineract_default -e \
"SELECT id, product_name FROM m_product_loan LIMIT 20;"
# Check configurations
mysql -u root -pmysql fineract_default -e \
"SELECT name, enabled FROM c_configuration WHERE name LIKE '%enable%';"
----
==== View Test Reports
After test execution, reports are available in:
[source]
----
fineract-e2e-tests-runner/build/cucumber-reports/
fineract-e2e-tests-runner/build/reports/tests/
----
== Best Practices
=== Test Organization
* Keep feature files focused on specific business domains
* Use descriptive scenario names
* Include business context in scenario descriptions
* Tag tests appropriately for organization and execution
=== Test Isolation
* Each test should be independent
* Don't rely on state from other tests
* Clean up test data in `@After` hooks
* Use unique identifiers for test entities
=== Test Data Management
* Use initialization for complex test data setup
* Create minimal required data in test scenarios
* Document test data dependencies
* Reuse database for multiple test runs when possible
=== Performance Optimization
* Run initialization once per database
* Use tags to run subset of tests during development
* Run full suite in CI/CD pipelines
* Consider parallel execution for large test suites
include::cucumber-cheatsheet.adoc[leveloffset=+1]
@@ -1,3 +1,805 @@
= Integration Testing
TBD
Integration tests in Apache Fineract validate the complete API layer and business logic by making HTTP calls to a running Fineract instance. These tests ensure that different components work together correctly and that the API behaves as expected.
== Overview
=== Architecture
* *Location*: `integration-tests/src/test/java/org/apache/fineract/integrationtests`
* *Framework*: JUnit 5 with REST Assured for HTTP communication
* *Base Class*: Most tests extend `BaseLoanIntegrationTest` or `IntegrationTest`
* *Client Library*: Uses Fineract client library for type-safe API interactions
* *Prerequisites*: Running Apache Fineract instance (default: https://localhost:8443)
=== Key Characteristics
* Tests run against a live Fineract instance
* Validates end-to-end API functionality
* Tests business logic, validation rules, and workflows
* Includes accounting verification and data integrity checks
* Uses real database transactions
* Tests can be run individually or as a suite
== Prerequisites
=== Required Software
* *Java 21*: Apache Fineract requires Java 21 (Azul Zulu JDK recommended)
* *Database*: MariaDB 11.5.2, PostgreSQL 17.4, or MySQL 9.1
* *Git*: For source code management
* *Gradle 8.14.3*: Included via wrapper
* *12GB RAM*: Recommended for test execution
=== Database Setup
Before running integration tests, ensure the databases are created:
[source,bash]
----
# Create required databases
./gradlew createDB -PdbName=fineract_tenants
./gradlew createDB -PdbName=fineract_default
----
=== Fineract Instance
Integration tests run fineract instance from cargo plugin by default.
== Configuration
=== Default Connection Settings
Integration tests use the following default connection settings:
[source,properties]
----
BACKEND_PROTOCOL=https
BACKEND_HOST=localhost
BACKEND_PORT=8443
BACKEND_USERNAME=mifos
BACKEND_PASSWORD=password
BACKEND_TENANT=default
----
=== Override Configuration
To override default values, set environment variables:
[source,bash]
----
# Set custom connection details
export BACKEND_PROTOCOL=http
export BACKEND_HOST=localhost
export BACKEND_PORT=8080
export BACKEND_USERNAME=admin
export BACKEND_PASSWORD=admin123
export BACKEND_TENANT=default
----
== Running Integration Tests
=== Complete Workflow
==== Step 1: Start Fineract
[source,bash]
----
# Start Fineract in background
./gradlew bootRun &
# Wait for startup (manual check)
curl -k https://localhost:8443/actuator/health
----
Expected response:
[source,json]
----
{"status":"UP"}
----
==== Step 2: Run Integration Tests
Navigate to the project root and execute tests:
[source,bash]
----
# Run all integration tests
./gradlew :integration-tests:test
# Run with clean build
./gradlew clean :integration-tests:test
----
=== Running Specific Tests
==== Run Single Test Class
[source,bash]
----
# Run entire test class
./gradlew :integration-tests:test --tests ClientLoanIntegrationTest
# Run with verbose output
./gradlew :integration-tests:test --tests ClientLoanIntegrationTest --info
----
==== Run Specific Test Method
[source,bash]
----
# Run single test method
./gradlew :integration-tests:test --tests ClientLoanIntegrationTest.testLoanSchedule
# Run multiple specific tests
./gradlew :integration-tests:test --tests ClientLoanIntegrationTest.testLoanSchedule \
--tests ClientLoanIntegrationTest.testLoanRepayment
----
==== Run Tests by Pattern
[source,bash]
----
# Run all loan-related tests
./gradlew :integration-tests:test --tests "*Loan*"
# Run all client-related tests
./gradlew :integration-tests:test --tests "*Client*"
# Run all accounting tests
./gradlew :integration-tests:test --tests "*Accounting*"
# Run all COB tests
./gradlew :integration-tests:test --tests "*COB*"
----
=== Advanced Test Execution
==== Run with Test Filtering
[source,bash]
----
# Run tests excluding specific packages
./gradlew :integration-tests:test --tests "*" \
--exclude "*Deprecated*"
# Run only fast tests (custom tag)
./gradlew :integration-tests:test --tests "*Fast*"
----
==== Parallel Execution
[source,bash]
----
# Run tests in parallel
./gradlew :integration-tests:test --parallel --max-workers=4
# Set custom thread count
./gradlew :integration-tests:test --parallel --max-workers=8
----
WARNING: Some integration tests may have dependencies on shared state. Use parallel execution carefully and ensure tests are properly isolated.
==== Run with Custom JVM Arguments
[source,bash]
----
# Increase heap size for large test suites
./gradlew :integration-tests:test -Xmx4g
# Enable debugging
./gradlew :integration-tests:test --debug-jvm
----
==== Generate Test Reports
[source,bash]
----
# Run tests and generate HTML reports
./gradlew :integration-tests:test
# Reports are generated at:
# integration-tests/build/reports/tests/test/index.html
----
==== Continuous Execution
[source,bash]
----
# Watch for changes and re-run tests
./gradlew :integration-tests:test --continuous
# Run specific test continuously
./gradlew :integration-tests:test --continuous --tests ClientLoanIntegrationTest
----
=== Test Execution Examples
==== Basic Loan Workflow Test
[source,bash]
----
# Test complete loan lifecycle
./gradlew :integration-tests:test --tests LoanApplicationTest
----
==== Progressive Loan Tests
[source,bash]
----
# Run all progressive loan tests
./gradlew :integration-tests:test --tests "*Progressive*"
----
==== Accounting Integration Tests
[source,bash]
----
# Run accounting-related tests
./gradlew :integration-tests:test --tests AccountingScenarioIntegrationTest
----
==== Business Date Tests
[source,bash]
----
# Run business date functionality tests
./gradlew :integration-tests:test --tests BusinessDateTest
----
==== Charge-Off Tests
[source,bash]
----
# Run charge-off related tests
./gradlew :integration-tests:test --tests "*ChargeOff*"
----
== Test Structure
=== BaseLoanIntegrationTest Overview
`BaseLoanIntegrationTest` is the comprehensive base test class for loan-related integration tests. It provides:
==== Pre-configured Loan Product Creation
[source,java]
----
// Create standard loan products
createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct()
create4IProgressive() // Progressive loan products
create4IProgressiveWithCapitalizedIncome() // With capitalized income
createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocation()
----
==== Transaction Management
[source,java]
----
// Validate loan transactions
verifyTransactions(loanId,
transaction(100.0, "Disbursement", "01 January 2024"),
transaction(50.0, "Repayment", "15 January 2024")
);
// Verify accounting journal entries
verifyJournalEntries(loanId, expectedEntries);
// Create transaction test data
Transaction txn = transaction(amount, type, date);
----
==== Loan Lifecycle Operations
[source,java]
----
// Disburse loan
disburseLoan(loanId, BigDecimal.valueOf(100), "01 January 2024");
// Undo disbursement
undoDisbursement(loanId);
// Re-age loan
reAgeLoan(loanId, reAgeRequest);
// Re-amortize loan
reAmortizeLoan(loanId, reAmortizeRequest);
// Execute Close of Business
executeInlineCOB(loanId);
----
==== Business Date Management
[source,java]
----
// Execute code at specific business date
runAt("01 January 2024", () -> {
Long loanId = applyAndApproveProgressiveLoan(...);
disburseLoan(loanId, BigDecimal.valueOf(100), "01 January 2024");
});
// Execute over date range
runFromToInclusive("01 January 2024", "31 January 2024", () -> {
// Operations for each date in the range
});
// Execute without bypass privileges
runAsNonByPass(() -> {
// Test operations with regular user permissions
});
----
==== Verification Methods
[source,java]
----
// Validate repayment schedule
verifyRepaymentSchedule(loanId, expectedSchedule);
// Check loan status
verifyLoanStatus(loanId, "ACTIVE");
// Verify outstanding amounts
verifyOutstanding(loanId, expectedOutstanding);
// Check arrears status
verifyArrears(loanId, expectedArrears);
----
=== Common Test Patterns
==== Test Setup Pattern
[source,java]
----
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder()
.setContentType(ContentType.JSON)
.build();
this.requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder()
.expectStatusCode(200)
.build();
}
----
==== Date-Specific Operations
[source,java]
----
runAt("01 January 2024", () -> {
// Create client
Long clientId = clientHelper.createClient(...);
// Apply for loan
Long loanId = applyLoan(clientId, productId, amount);
// Approve loan
approveLoan(loanId, "01 January 2024");
// Disburse loan
disburseLoan(loanId, amount, "01 January 2024");
});
----
==== Structured Verification
[source,java]
----
// Verify transactions
verifyTransactions(loanId,
transaction(100.0, "Disbursement", "01 January 2024"),
transaction(50.0, "Capitalized Income", "01 January 2024"),
transaction(0.55, "Capitalized Income Amortization", "01 January 2024")
);
// Verify journal entries using convenience methods
verifyJournalEntries(loanId,
debit(account.getLoansReceivable(), 100.0),
credit(account.getSuspenseClearingAccount(), 100.0)
);
// Or using full journalEntry method with Account objects
verifyJournalEntries(loanId,
journalEntry(100.0, account.getLoansReceivable(), "DEBIT"),
journalEntry(100.0, account.getSuspenseClearingAccount(), "CREDIT")
);
----
==== Progressive Loan Testing
[source,java]
----
// Create progressive loan product
Long productId = create4IProgressive();
// Apply and approve
Long loanId = applyAndApproveProgressiveLoan(clientId, productId,
amount, numberOfRepayments, interestRate);
// Test advanced features
testCapitalizedIncome(loanId);
testDownPayment(loanId);
testAdvancedPaymentAllocation(loanId);
----
== Writing Integration Tests
=== Test Development Guidelines
==== 1. Create Test Class
Create a new test class in `integration-tests/src/test/java/org/apache/fineract/integrationtests/`:
[source,java]
----
package org.apache.fineract.integrationtests;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
public class MyNewFeatureIntegrationTest extends BaseLoanIntegrationTest {
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder()
.setContentType(ContentType.JSON)
.build();
this.requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder()
.expectStatusCode(200)
.build();
}
@Test
public void testMyNewFeature() {
// Test implementation
}
}
----
==== 2. Use Helper Classes
Leverage existing helper classes:
[source,java]
----
// Client operations
ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec);
Long clientId = clientHelper.createClient(...);
// Loan operations
LoanTransactionHelper loanHelper = new LoanTransactionHelper(requestSpec, responseSpec);
Long loanId = loanHelper.applyLoan(...);
// Account operations
AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec);
// Business date operations
BusinessDateHelper businessDateHelper = new BusinessDateHelper();
businessDateHelper.updateBusinessDate(...);
// COB operations
InlineLoanCOBHelper cobHelper = new InlineLoanCOBHelper(requestSpec, responseSpec);
cobHelper.executeInlineCOB(loanId);
----
==== 3. Follow Best Practices
* *Self-Contained Tests*: Each test should be independent
* *Clear Setup*: Use `@BeforeEach` for test initialization
* *Date Management*: Use `runAt()` for consistent date-based testing
* *Comprehensive Verification*: Verify transactions, schedules, and accounting
* *Helper Methods*: Use provided helper classes rather than direct API calls
* *Error Testing*: Test both positive and negative scenarios
* *Cleanup*: Clean up test data when necessary
==== 4. Test Complex Scenarios
[source,java]
----
@Test
public void testLoanWithMultipleDisbursements() {
runAt("01 January 2024", () -> {
// Create client
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
// Create multi-disbursement loan product
Long productId = createMultiDisbursementProduct();
// Apply for loan
Long loanId = applyAndApproveProgressiveLoan(clientId, productId,
BigDecimal.valueOf(1000), 12, BigDecimal.valueOf(10));
// First disbursement
disburseLoan(loanId, BigDecimal.valueOf(500), "01 January 2024");
// Verify first disbursement
verifyTransactions(loanId,
transaction(500.0, "Disbursement", "01 January 2024")
);
});
runAt("15 January 2024", () -> {
// Second disbursement
disburseLoan(loanId, BigDecimal.valueOf(500), "15 January 2024");
// Verify both disbursements
verifyTransactions(loanId,
transaction(500.0, "Disbursement", "01 January 2024"),
transaction(500.0, "Disbursement", "15 January 2024")
);
// Verify outstanding balance
verifyOutstanding(loanId, BigDecimal.valueOf(1000));
});
}
----
==== 5. Run and Verify
[source,bash]
----
# Run your new test
./gradlew :integration-tests:test --tests MyNewFeatureIntegrationTest
# Run with verbose output for debugging
./gradlew :integration-tests:test --tests MyNewFeatureIntegrationTest --info
# Run specific test method
./gradlew :integration-tests:test --tests MyNewFeatureIntegrationTest.testMyNewFeature
----
== Troubleshooting
=== Common Test Failures
==== Connection Issues
*Symptom*: Tests fail with connection refused errors
*Solutions*:
[source,bash]
----
# Verify Fineract is running
curl -k https://localhost:8443/actuator/health
# Check if port is available
netstat -tulpn | grep 8443
# Check Fineract logs (logs go to console/stdout)
# If running in background with output redirection:
# tail -f build/bootRun.log
# Restart Fineract if needed
pkill -f bootRun
./gradlew bootRun &
----
==== Authentication Failures
*Symptom*: Tests fail with 401 or 403 errors
*Solutions*:
[source,bash]
----
# Check default credentials
mysql -u root -pmysql fineract_default -e \
"SELECT username, password FROM m_appuser WHERE username = 'mifos';"
# Reset credentials if needed
mysql -u root -pmysql fineract_default -e \
"UPDATE m_appuser SET password = '5jdQ3dNQXHPzCuBbZVdQZ2XnVlPc3l2l' \
WHERE username = 'mifos';"
# Verify connection settings
echo "Protocol: ${BACKEND_PROTOCOL:-https}"
echo "Host: ${BACKEND_HOST:-localhost}"
echo "Port: ${BACKEND_PORT:-8443}"
----
==== Data Inconsistency
*Symptom*: Tests fail due to unexpected data state
*Solutions*:
[source,bash]
----
# Reset database
mysql -u root -pmysql -e "DROP DATABASE fineract_default;"
mysql -u root -pmysql -e "DROP DATABASE fineract_tenants;"
# Recreate databases
./gradlew createDB -PdbName=fineract_tenants
./gradlew createDB -PdbName=fineract_default
# Restart Fineract
pkill -f bootRun
./gradlew bootRun &
----
==== Test Timeout
*Symptom*: Tests hang or timeout
*Solutions*:
[source,bash]
----
# Increase test timeout
./gradlew :integration-tests:test -Dtest.timeout=600
# Check for database locks
mysql -u root -pmysql fineract_default -e "SHOW PROCESSLIST;"
# Kill long-running queries
mysql -u root -pmysql fineract_default -e "KILL <process_id>;"
----
==== Memory Issues
*Symptom*: OutOfMemoryError during test execution
*Solutions*:
[source,bash]
----
# Increase heap size
./gradlew :integration-tests:test -Xmx4g -Xms2g
# Run fewer tests in parallel
./gradlew :integration-tests:test --max-workers=2
# Clean build directory
./gradlew clean
----
=== Debugging Tips
==== Enable Detailed Logging
[source,bash]
----
# Run with debug output
./gradlew :integration-tests:test --debug
# Run with info level
./gradlew :integration-tests:test --info
# Save output to file
./gradlew :integration-tests:test --info > test-output.log 2>&1
----
==== Check Test Reports
After test execution, detailed reports are available:
[source]
----
# HTML report
integration-tests/build/reports/tests/test/index.html
# XML reports (for CI/CD)
integration-tests/build/test-results/test/
# Gradle scan (upload for detailed analysis)
----
Generate Gradle build scan:
[source,bash]
----
./gradlew :integration-tests:test --scan
----
==== Database State Verification
[source,bash]
----
# Check loan status
mysql -u root -pmysql fineract_default -e \
"SELECT id, account_no, loan_status_id, principal_amount \
FROM m_loan ORDER BY id DESC LIMIT 10;"
# Check transactions
mysql -u root -pmysql fineract_default -e \
"SELECT loan_id, transaction_type_enum, amount, transaction_date \
FROM m_loan_transaction WHERE loan_id = <loan_id>;"
# Check journal entries
mysql -u root -pmysql fineract_default -e \
"SELECT entry_date, account_id, type_enum, amount \
FROM acc_gl_journal_entry WHERE loan_id = <loan_id>;"
# Check configurations
mysql -u root -pmysql fineract_default -e \
"SELECT name, enabled FROM c_configuration \
WHERE name LIKE '%business%' OR name LIKE '%enable%';"
----
==== API Response Debugging
Add logging to test methods:
[source,java]
----
Response response = loanHelper.applyLoan(...);
System.out.println("Response: " + response.asString());
System.out.println("Status Code: " + response.getStatusCode());
// Or use logger
log.info("Response: {}", response.asString());
----
==== Isolate Failing Tests
[source,bash]
----
# Run only the failing test
./gradlew :integration-tests:test --tests FailingTest --info
# Run with rerun-tasks option
./gradlew :integration-tests:test --tests FailingTest --rerun-tasks
# Run with fail-fast to stop on first failure
./gradlew :integration-tests:test --fail-fast
----
== Best Practices
=== Test Organization
* Extend appropriate base classes (`BaseLoanIntegrationTest`, `IntegrationTest`)
* Use descriptive test method names that explain what is being tested
* Group related tests in the same test class
* Use `@BeforeEach` for setup and `@AfterEach` for cleanup
* Follow existing naming conventions
=== Test Isolation
* Each test should be independent and not rely on other tests
* Create fresh test data for each test
* Clean up test data after test execution
* Use unique identifiers to avoid conflicts
* Don't share mutable state between tests
=== Performance Optimization
* Reuse Fineract instance across test runs
* Use `runAt()` for efficient date management
* Minimize unnecessary API calls
* Use bulk operations when appropriate
* Consider parallel execution for independent tests
* Run subset of tests during development
=== Code Quality
* Follow existing code patterns and conventions
* Use helper methods instead of duplicating code
* Add comments for complex business logic
* Verify both positive and negative scenarios
* Include edge cases in test coverage
* Document test assumptions and prerequisites
=== Comprehensive Verification
* Always verify transaction creation
* Check accounting journal entries
* Validate repayment schedules
* Verify loan status transitions
* Test charge applications
* Validate business date handling
* Check error messages for validation failures
=== Maintenance
* Update tests when API changes
* Remove deprecated test methods
* Keep test data realistic
* Document complex test scenarios
* Review and refactor tests regularly
* Keep tests aligned with current best practices