GSoC 2026 Project Thread: Backend Integration Testing Standardization and Expansion

Hello OpenELIS Community!

I will be giving my updates on the Backend Integration Testing project for GSoC 2026. Building on the foundation laid during the contributions from the previous couple of years where we established the core infrastructure and reached a 28% coverage baseline, this year’s project aims to scale up significantly, targeting a 60%+ coverage goal for all critical backend components.

Project Goal

This project aims at extending and creating more Integration Tests to achieve a Test Coverage of at least 60% for the Backend Service and Controller Layer

To standardize the backend integration testing workflow, increase test coverage across the 5-layer architecture (Service, DAO, Controller), and implement complex workflow-based integration tests using modern infrastructure (Testcontainers, DBUnit)

Project Background (Evolved from previous community contributions)

For the 2 last years, we established a Dataset-Driven Testing Strategy, ensuring realistic scenarios by using actual data instead of mocks. This year, we are moving beyond simple CRUD tests to focus on deep business logic and cross-component interactions.

Current Gaps

After an initial analysis of the current state, I’ve identified several key areas for GSoC 2026:

  1. Controller Layer Coverage: Many REST and Spring MVC controllers are currently excluded from component scanning in the test context (AppTestConfig) to prevent circular dependencies. This leaves a gap in testing the web layer’s interaction with the service layer.
  2. Infrastructure Mocking Boundaries: While we successfully mock external services (FHIR, Odoo, Mail) for isolation, we lack standardized patterns for verifying the outgoing data structure to these systems.
  3. Complex Workflow State: Most tests reset the database state between methods. This makes it difficult to test multi-step laboratory workflows (e.g., Sample Receipt → Analysis → Result → Validation → Release) as a single cohesive unit.
  4. Data Setup Friction: Manually creating DBUnit XML datasets for complex entities like Samples and Patients remains a bottleneck.
  5. Standardization: Some older integration tests do not yet follow the “Gold Standard” (based on MenuServiceTest.java), leading to inconsistent test reliability.

Proposed Implementation Plan

My project will be divided into the following key milestones:

Milestone 1: Infrastructure & Standardization

  • Refactor AppTestConfig to support more granular bean registration, allowing more controllers to be tested via MockMvc.
  • Audit and migrate existing integration tests to the BaseWebContextSensitiveTest standard.
  • JaCoCo Integration: Ensure JaCoCo is strictly measuring and tracking coverage for all newly added tests to maintain data-driven progress.

Milestone 2: Core Service & Controller Expansion

  • Increase integration test coverage for the Patient, Sample, and Result modules.
  • Ensure comprehensive coverage of CRUD operations, data retrieval, business logic validation, and error handling.
  • Goal: Happy-path and error-path integration tests for every REST endpoint in these modules.

Milestone 3: Complex Workflow Testing

  • Implement “Scenario Tests” that simulate full laboratory cycles.
  • Develop shared datasets (full-lab-workflow.xml) that can be used across multiple test suites to ensure data integrity across the entire stack.

Milestone 4: Tooling & Documentation

  • Explore/Implement a Java-based DSL or Builder pattern for generating test data dynamically.
  • Finalize the “OpenELIS Integration Testing Handbook” for future contributors.

Stay Updated

I will be documenting my journey, technical hurdles, and milestones regularly. You can follow my progress and read detailed deep-dives on my Medium blog.

:light_bulb: Discussion Points

I would love to hear from mentors and community members:

  • Are there specific “high-risk” modules that you feel are currently under-tested?
  • What are the most common “integration bugs” you’ve encountered that unit tests missed?
  • Any preferences on data generation tools?

Looking forward to your feedback and ideas
cc: @Herbert @pmanko @Moses_Mutesasira @reagan @tasksolver @mherman22

4 Likes

Milestone 1 Update: Integration Test Dataset Audit Results

Hi everyone
As promised in Milestone 1, I’ve completed the first phase of the audit: identifying which existing integration tests follow the “Gold Standard” (MenuServiceTest.java) and which ones still need to be brought in line.

What I Audited

I scanned every test class that extends BaseWebContextSensitiveTest and checked whether it calls executeDataSetWithStateManagement(...) in a @Before method to seed a known, isolated database state.

The convention is:

java

@Before

public void init() throws Exception {

executeDataSetWithStateManagement("testdata/<module>.xml");

}

The Finding: 40 Tests Are Missing Datasets

I found 40 integration tests that inherit the full Spring + Testcontainers + Liquibase context (via BaseWebContextSensitiveTest) but have no dataset loading at all. This means they run against an unknown or empty database state, making their results unreliable.

Some create data programmatically inside @Before or @Test (which bypasses isolation), and in one case, ShippingBoxServiceTest.java even has a comment in the code itself that says:

“Note: This test would require test data setup via @Before method”


Affected Tests (40 total, grouped by module)

Analyzer Module (18 tests)

analyzer/AnalyzerColumnMappingTest.java

analyzer/DatabaseSchemaValidationTest.java

analyzer/UnitMappingColumnMappingTest.java

analyzer/controller/AnalyzerDefaultsRestControllerTest.java

analyzer/controller/AnalyzerMappingPreviewRestControllerTest.java

analyzer/controller/AnalyzerPluginConfigRestControllerTest.java

analyzer/controller/AnalyzerRestControllerTest.java

analyzer/controller/AnalyzerSecurityTest.java

analyzer/controller/AuthenticatedAnalyzerControllerTest.java

analyzer/genexpert/GeneXpertFileIntegrationTest.java

analyzer/genexpert/GeneXpertHL7IntegrationTest.java

analyzer/mindray/MindrayBA88AIntegrationTest.java

analyzer/service/AnalyzerErrorServiceIntegrationTest.java

analyzer/service/AnalyzerFieldMappingServiceIntegrationTest.java

analyzer/service/AnalyzerMappingAuditTest.java

analyzer/service/AnalyzerQueryServiceIntegrationTest.java

analyzer/service/AnalyzerQueryServiceStoreFieldsIntegrationTest.java

analyzer/service/CustomFieldTypeValidationIntegrationTest.java

AnalyzerImport Module (5 tests)

analyzerimport/action/AnalyzerFhirImportControllerTest.java

analyzerimport/analyzerreaders/ASTMAnalyzerReaderIdentificationTest.java

analyzerimport/analyzerreaders/HL7AnalyzerReaderTest.java

analyzerimport/analyzerreaders/StagoSTart4MessageProcessingIntegrationTest.java

analyzerimport/controller/AnalyzerImportControllerHL7Test.java

Patient Module (3 tests)

patient/daoimpl/PatientDAORedirectIntegrationTest.java

patient/merge/dao/PatientMergeAuditDAOTest.java

patient/merge/service/PatientLookupRedirectTest.java

Storage Module (DAO Layer 3 tests)

storage/dao/StorageDeviceDAOTest.java

storage/dao/StorageRackDAOTest.java

storage/dao/StorageShelfDAOTest.java

Other Modules (11 tests)

alert/service/AlertNotificationServiceTest.java

barcode/BarcodeConfigurationRestControllerTest.java

common/provider/validation/NonConformityRecordNumberValidationProviderTest.java

ocl/OclZipImporterIntegrationTest.java

plugin/StagoSTart4PluginIntegrationTest.java

qc/service/WestgardRuleConfigServiceIntegrationTest.java

reports/controller/BaseReportRestControllerTest.java

shipment/ShippingBoxServiceTest.java

shipment/UnassignedSampleServiceTest.java

sitebranding/controller/rest/SiteBrandingRestControllerTest.java

sitebranding/dao/SiteBrandingDAOTest.java

Why This Matters Before We Add More Tests

  1. Coverage numbers are misleading JaCoCo counts these as “tested” code paths even when the test isn’t actually asserting anything meaningful against real data.
  2. No isolation tests that create data programmatically can leave state that causes other tests to fail intermittently.
  3. Harder to extend any new test we add on top of these modules will inherit the same fragile patterns if we don’t fix the baseline first.

My Proposed Plan of Action

I’d like to treat these 40 tests as a prerequisite cleanup before expanding coverage (aligns directly with Milestone 1 “Audit and migrate existing integration tests to the BaseWebContextSensitiveTest standard”).

I am taking ownership of:

  • Analyzer module (18 tests)
  • AnalyzerImport module (5 tests)

For each test, the fix is:

  1. Create src/test/resources/testdata/<module>.xml (DBUnit Flat XML with minimum required records)
  2. Add @Before public void init() calling executeDataSetWithStateManagement("testdata/<module>.xml")
  3. Remove any programmatic data creation and replace with references to seeded records
  4. I have created a summurised report of the current structure of our current tests
    @Herbert @Moses_Mutesasira @mherman22 @pmanko
    Happy to answer any questions.
1 Like

Hello everyone,

Quick update covering the bonding period and the start of Week 1 of coding.

Bonding period

During the bonding period I focused on understanding the testing infrastructure, and working on some early integration testing tasks. I also wrote a short reflection on what I learned during that phase especially around collaboration and how tests are structured in the project.

Read the blog post — Agaba Derrick on Medium

Week 1 of coding (what’s underway)

We’re now in the first week of the coding period. So far:

  • Had a strategy session with my mentor @Herbert on how to approach the testing work going forward.
  • Continuing integration testing working on fixture stabilisation and improving dataset consistency.
  • Strengthening test coverage across key service-layer components.
  • Raised several pull requests around test improvements and codebase cleanup.

Pull Requests

Raised multiple PRs this week targeting test improvements and codebase cleanup:

CC: @Moses_Mutesasira @mherman22 @pmanko

2 Likes

Week 2 Update — Test Framework Hardening (Phase 1 complete)

Hey community

Week 2 is done and I want to share what kept me busy.

The main focus: BaseWebContextSensitiveTest

Before we can introduce the two-layer hierarchy (Milestone 1), the root class that every integration test inherits from needed to be solid. My mentor(s) Herbert and Herman and I agreed to fix the foundation first, then build on it. That became PR #3700.

5 fixes applied (Phase 1):

  1. setUp() was missing @Before any subclass that didn’t call super.setUp() got a silent null MockMvc with no useful error
  2. cleanRowsInCurrentConnection was leaking a DBUnit wrapper connection on every dataset load in a large suite this quietly exhausts the pool
  3. mapToJson / mapFromJson were creating a new ObjectMapper on every call, triggering a full Jackson module scan each time replaced with a single private static final instance
  4. buildFhirRequest hardcoded the integer 1 as system user id replaced with the existing TEST_SYS_USER_ID constant
  5. DBUnit connection config was inlined extracted to a private buildDbUnitConnection() helper so it never needs to be duplicated

A deeper bug that surfaced during this work:

reference_tables is seeded by Liquibase at DB init with ~136 rows. Every audit-emitting service does a lookup keyed on ref_table_name. If any fixture loader truncates that table and only reinserts its own handful of rows, every audit call downstream blows up with “Reference Table is null” order-dependent, masked in isolation, only visible when tests run in sequence. The fix was a PROTECTED_SEED_TABLES filter that makes the seed untouchable at load time regardless of what any fixture XML declares. This came out of the PR #3591 audit-emit work and was the trickiest thing to track down this week.

Other PRs from the week:

  • #3644 SampleTypeRequestServiceTest approved :white_check_mark:
  • #3656 ShipmentServiceTest
  • #3657 JPA field addition to Shipment.java needed for the above
  • #3662 BoxSampleItemServiceTest
  • #3638 Draft: porting fixture violations found across the suite

Week 3 plan:

Phase 2 introduce BaseContextSensitiveTest as a parent class sitting between the Spring context wiring and the full web-layer class. This separates service-layer tests (no MockMvc needed) from controller-layer tests (full servlet context). Same structure OpenMRS uses. No existing subclass should need to change its extends declaration.

Also continuing to expand coverage for service classes that currently have none.

cc: @Herbert @pmanko @Moses_Mutesasira @tasksolver @mherman22

1 Like

Week 3 Update Foundation review (Phase 2 moved to Week 4)

Hey community, sorry for the delay on this one, I was away over the weekend and couldn’t get it out on schedule.

The main focus: reviewing #3712, not writing new test infra

Coming out of Week 2, the plan was to start Phase 2: introducing BaseContextSensitiveTest as the parent class sitting between Spring’s context wiring and the full web-layer test class. That didn’t ship this week, and here’s why.

Piotr opened #3712, a large rework of how the entire integration suite handles database isolation between tests replacing the hand-built truncate-and-reseed model with real per-test transactional rollback. The core fix is a new TransactionAwareTestDataSource that forces JPA, raw JDBC, and the DBUnit fixture loader to share one connection per test instead of quietly fighting over separate ones, which turned out to be the root cause behind a string of flaky, order-dependent CI failures. Result on his branch: 4404 tests, 0 failures, 0 errors.

That PR introduces two new base classes (BaseWebContextSensitiveTest and BaseCommittedFixtureTest) sitting in exactly the layer my planned BaseContextSensitiveTest hierarchy needs to sit on top of. Building it in parallel without fully understanding how #3712 changes that layer risked either duplicating work or shipping something that conflicts with it the moment both land. So most of this week went into reading it closely and giving review feedback instead of writing new test infrastructure on ground that was actively shifting underneath it.

Two smaller PRs also wrapped up:

  • #3710 Fix: fhir qc and storage dbunit errors — approved. Fixed a stale AnalyzerTestNameCache that was silently dropping valid FHIR QC bundles, moved control-lot setup into the standard DBUnit dataset, and fixed an intermittent CI-only storage location failure caused by a fixture missing a system_user row.
  • #3719 test: add system user integration test — opened, still in review.

Week 4 plan:

The Week 2 plan still stands, just a week later than hoped. Introducing BaseContextSensitiveTest as the parent class between Spring’s context wiring and the full web-layer test class, separating service-layer tests (no MockMvc needed) from controller-layer tests (full servlet context) — same structure OpenMRS uses. Now with a much clearer picture of how it sits on top of the isolation model landing in #3712. Also continuing to expand integration test coverage for service classes that currently have none.

cc: @Herbert @pmanko @Moses_Mutesasira @tasksolver @mherman22