From cb7ead208f05ff8ba8b5dfa5d0712f49d6702791 Mon Sep 17 00:00:00 2001 From: Attila Budai Date: Tue, 30 Sep 2025 13:55:46 +0200 Subject: [PATCH] FINERACT-1724: Document integration and e2e testing in fineract --- .../docs/en/chapters/testing/cucumber.adoc | 554 +++++++++++- .../docs/en/chapters/testing/integration.adoc | 804 +++++++++++++++++- 2 files changed, 1355 insertions(+), 3 deletions(-) diff --git a/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc b/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc index 272fc8953..40b63cf90 100644 --- a/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc +++ b/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc @@ -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] diff --git a/fineract-doc/src/docs/en/chapters/testing/integration.adoc b/fineract-doc/src/docs/en/chapters/testing/integration.adoc index b802ec0ef..2df8236d4 100644 --- a/fineract-doc/src/docs/en/chapters/testing/integration.adoc +++ b/fineract-doc/src/docs/en/chapters/testing/integration.adoc @@ -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 ;" +---- + +==== 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 = ;" + +# 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 = ;" + +# 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