Skip to content

test: convert eligible integration tests to @Transactional#24014

Merged
stian-sandvold merged 2 commits into
masterfrom
chore/convert-non-tx-it-tierAB
Jun 1, 2026
Merged

test: convert eligible integration tests to @Transactional#24014
stian-sandvold merged 2 commits into
masterfrom
chore/convert-non-tx-it-tierAB

Conversation

@stian-sandvold
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #24012. Converts a further 7 non-@Transactional integration tests in
dhis-test-integration to @Transactional, so SpringIntegrationTestExtension cleans up
between tests via a cheap transaction rollback instead of the expensive
dbmsManager.emptyDatabase() truncate-all that runs once per test method for
non-transactional, PER_METHOD classes.

Where a test reads back through raw jdbcTemplate SQL (which does not trigger Hibernate
auto-flush) or writes via session.doWork on the shared connection, an explicit
entityManager.flush() is added so those reads see the un-committed session writes within the
same transaction. jdbcTemplate and Hibernate share the transaction-bound connection under
@Transactional, so a flush (not a commit) is sufficient.

This was the careful, per-class audit called for in improvement plan #2 — each conversion was
verified green individually; classes that could not be safely converted were left unchanged with
the reason recorded (see below).

Changes (file by file)

File Change Why it needed it
PostgreSqlBuilderInheritanceIntegrationTest @Transactional only Pure DDL via jdbcTemplate; transactional in Postgres, self-cleans.
PredictorServiceTest @Transactional + flush before the delete-veto assertThrows DeleteNotAllowedException is an app-level veto backed by a raw-SQL count on predictor; the predictor rows must be flushed first.
ProgramStageDataElementServiceTest @Transactional + 2 flushes Delete-veto raw-SQL query, and a jdbc store read (getProgramStageDataElementsWithSkipSynchronizationSetToTrue).
DataOrgUnitMergeHandlerTest @Transactional + flush in setUp & addDataApprovals Counts/merge run raw NamedParameterJdbcTemplate; approvals written via Hibernate must be flushed.
DataApprovalStoreUserTest @Transactional + flush before read HibernateDataApprovalStore.getDataApprovalStatuses is raw jdbc.
DataApprovalServiceCategoryOptionGroupTest @Transactional + flush in setUp & in approve/unapprove/accept/unaccept helpers Status reads go through raw-jdbc store; flushing inside the write helpers covers the interleaved approve→read cycles.
DataStatisticsEventStoreTest @Transactional + flush at end of setUp HibernateDataStatisticsEventStore reads are raw jdbc.

Performance

Single shared-context JVM, surefire per-class "Time elapsed", master baseline vs branch:

Converted class before after Δ
PredictorServiceTest 4.069s 1.785s −56%
ProgramStageDataElementServiceTest 2.324s 1.126s −52%
DataStatisticsEventStoreTest 1.277s 0.617s −52%
DataOrgUnitMergeHandlerTest 0.663s 0.306s −54%
PostgreSqlBuilderInheritanceIntegrationTest 0.783s 0.430s −45%
DataApprovalStoreUserTest 0.501s 0.367s −27%

Sum of those 6 separable classes: 9.62s → 4.63s (−51.9%) — each roughly halves, consistent
with removing one emptyDatabase() truncate per test method. (DataApprovalServiceCategoryOptionGroupTest
also converted, but it absorbs the one-time Spring context startup in a shared-JVM run, so its own
saving isn't separable from the startup it carries.)

Caveat: single run, shared-context JVM; the full-suite sum is dominated by the one ~startup the
run carries, so the per-class deltas are the meaningful signal. Real CI (sharded, per-shard
startup) will differ in absolute terms — the win is removing work done, not just wall-clock.

Deferred (audited, intentionally NOT converted)

  • Async tracker exporter (reads TEs on a separate thread pool / connection in
    TrackedEntityAggregate.find() → can't see un-committed data, flush can't bridge):
    PotentialDuplicateRemoveTrackedEntityTest, DeduplicationServiceMergeIntegrationTest,
    MaintenanceServiceTest.
  • Spring exception-translation gap (assertThrows expects DataIntegrityViolationException,
    but a raw em.flush() throws untranslated jakarta.persistence.PersistenceException;
    translation only happens at tx commit / via @Repository proxy):
    TrackedEntityTypeServiceTest, UidDBConstraintCheckTest.
  • idScheme / import-pipeline metadata resolution (code/attribute-based lookups and the import
    preheat don't see un-committed data even with a setUp flush — import returns 0 rows):
    AdxDataServiceIntegrationTest, DataExportServiceExportTest, DataExportServiceIntegrationTest.

Test plan

  • mvn test -pl dhis-test-integration -am -Pintegration-test -Dtest=<7 classes> → BUILD SUCCESS, all green
  • Each converted class verified green; failing candidates reverted, not shipped
  • mvn spotless:check -pl dhis-test-integration passes
  • CI green on JDK 17

🤖 Generated with Claude Code

Convert 7 non-@transactional integration tests to @transactional so the
SpringIntegrationTestExtension cleans up via cheap rollback instead of the
expensive dbmsManager.emptyDatabase() truncate-all run per test method.

Tests whose reads go through raw jdbcTemplate SQL (or whose write happens via
session.doWork on the shared connection) get an explicit entityManager.flush()
so those reads see the un-committed session writes within the same transaction.

Measured ~52% reduction in per-class execution time on the 6 separable converted
classes in a shared-context run (each ~halves, consistent with eliminating one
emptyDatabase() truncate per test method).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@stian-sandvold stian-sandvold marked this pull request as ready for review May 29, 2026 11:51
@sonarqubecloud
Copy link
Copy Markdown

@stian-sandvold stian-sandvold merged commit df93999 into master Jun 1, 2026
22 checks passed
@stian-sandvold stian-sandvold deleted the chore/convert-non-tx-it-tierAB branch June 1, 2026 07:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants