Skip to content

Commit c8f8f2f

Browse files
committed
fix(tests): resolve flaky Testcontainers lifecycle causing intermittent CI failures
Move @container fields to companion objects in all @testinstance(PER_CLASS) test classes so Testcontainers manages the container lifecycle correctly (static/shared scope instead of per-instance). Also set maxParallelForks=1 to prevent Exposed's global Database registry from being overwritten by concurrently running test classes, which caused random PSQLException: Connection refused errors on CI.
1 parent aaca2e2 commit c8f8f2f

9 files changed

Lines changed: 61 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
- **Flaky backend tests** — Resolved intermittent `PSQLException: Connection refused` errors in CI by fixing Testcontainers lifecycle mismatch (`@Container` moved to `companion object` for all `@TestInstance(PER_CLASS)` test classes) and disabling parallel test-class execution (`maxParallelForks = 1`) to prevent Exposed's global database registry from being overwritten mid-test by a concurrently running test class
12+
1013
## [6.3.0] - 2026-03-30
1114

1215
### Added

backend/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,5 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach
8686

8787
tasks.test {
8888
useJUnitPlatform()
89+
maxParallelForks = 1
8990
}

backend/src/test/kotlin/com/shoppinglist/repository/ItemRepositoryTest.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ import kotlin.test.assertTrue
2323
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2424
class ItemRepositoryTest {
2525

26-
@Container
27-
private val postgres = PostgreSQLContainer("postgres:15-alpine")
28-
.withDatabaseName("test_shopping_lists")
29-
.withUsername("test_user")
30-
.withPassword("test_pass")
26+
companion object {
27+
@Container
28+
@JvmField
29+
val postgres = PostgreSQLContainer("postgres:15-alpine")
30+
.withDatabaseName("test_shopping_lists")
31+
.withUsername("test_user")
32+
.withPassword("test_pass")
33+
}
3134

3235
private lateinit var database: Database
3336
private lateinit var listRepository: ListRepository

backend/src/test/kotlin/com/shoppinglist/repository/ListRepositoryTest.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ import kotlin.test.assertTrue
2424
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2525
class ListRepositoryTest {
2626

27-
@Container
28-
private val postgres = PostgreSQLContainer("postgres:15-alpine")
29-
.withDatabaseName("test_shopping_lists")
30-
.withUsername("test_user")
31-
.withPassword("test_pass")
27+
companion object {
28+
@Container
29+
@JvmField
30+
val postgres = PostgreSQLContainer("postgres:15-alpine")
31+
.withDatabaseName("test_shopping_lists")
32+
.withUsername("test_user")
33+
.withPassword("test_pass")
34+
}
3235

3336
private lateinit var database: Database
3437
private lateinit var listRepository: ListRepository

backend/src/test/kotlin/com/shoppinglist/repository/PushSubscriptionRepositoryTest.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ import kotlin.test.assertTrue
2727
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2828
class PushSubscriptionRepositoryTest {
2929

30-
@Container
31-
private val postgres = PostgreSQLContainer("postgres:15-alpine")
32-
.withDatabaseName("test_shopping_lists")
33-
.withUsername("test_user")
34-
.withPassword("test_pass")
30+
companion object {
31+
@Container
32+
@JvmField
33+
val postgres = PostgreSQLContainer("postgres:15-alpine")
34+
.withDatabaseName("test_shopping_lists")
35+
.withUsername("test_user")
36+
.withPassword("test_pass")
37+
}
3538

3639
private lateinit var database: Database
3740
private lateinit var listRepository: ListRepository

backend/src/test/kotlin/com/shoppinglist/repository/TransactionTest.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ import kotlin.test.assertTrue
2727
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2828
class TransactionTest {
2929

30-
@Container
31-
private val postgres = PostgreSQLContainer("postgres:15-alpine")
32-
.withDatabaseName("test_shopping_lists")
33-
.withUsername("test_user")
34-
.withPassword("test_pass")
30+
companion object {
31+
@Container
32+
@JvmField
33+
val postgres = PostgreSQLContainer("postgres:15-alpine")
34+
.withDatabaseName("test_shopping_lists")
35+
.withUsername("test_user")
36+
.withPassword("test_pass")
37+
}
3538

3639
private lateinit var database: Database
3740
private lateinit var listRepository: ListRepository

backend/src/test/kotlin/com/shoppinglist/routes/ListRoutesTest.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@ import kotlin.test.assertTrue
2828
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2929
class ListRoutesTest {
3030

31-
@Container
32-
private val postgres = PostgreSQLContainer("postgres:15-alpine")
33-
.withDatabaseName("test_shopping_lists")
34-
.withUsername("test_user")
35-
.withPassword("test_pass")
31+
companion object {
32+
@Container
33+
@JvmField
34+
val postgres = PostgreSQLContainer("postgres:15-alpine")
35+
.withDatabaseName("test_shopping_lists")
36+
.withUsername("test_user")
37+
.withPassword("test_pass")
38+
}
3639

3740
private lateinit var database: Database
3841
private val json = Json { ignoreUnknownKeys = true }

backend/src/test/kotlin/com/shoppinglist/service/CleanupServiceTest.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ import kotlin.test.assertNull
2727
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2828
class CleanupServiceTest {
2929

30-
@Container
31-
private val postgres = PostgreSQLContainer("postgres:15-alpine")
32-
.withDatabaseName("test_cleanup")
33-
.withUsername("test_user")
34-
.withPassword("test_pass")
35-
30+
companion object {
31+
@Container
32+
@JvmField
33+
val postgres = PostgreSQLContainer("postgres:15-alpine")
34+
.withDatabaseName("test_cleanup")
35+
.withUsername("test_user")
36+
.withPassword("test_pass")
37+
}
38+
3639
private lateinit var database: Database
3740
private lateinit var cleanupService: CleanupService
3841
private lateinit var listRepository: ListRepositoryImpl

backend/src/test/kotlin/com/shoppinglist/websocket/WebSocketIntegrationTest.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ import kotlin.test.assertTrue
3030
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
3131
class WebSocketIntegrationTest {
3232

33-
@Container
34-
private val postgres = PostgreSQLContainer("postgres:15-alpine")
35-
.withDatabaseName("test_shopping_lists")
36-
.withUsername("test_user")
37-
.withPassword("test_pass")
33+
companion object {
34+
@Container
35+
@JvmField
36+
val postgres = PostgreSQLContainer("postgres:15-alpine")
37+
.withDatabaseName("test_shopping_lists")
38+
.withUsername("test_user")
39+
.withPassword("test_pass")
40+
}
3841

3942
private lateinit var database: Database
4043
private val json = Json { ignoreUnknownKeys = true }

0 commit comments

Comments
 (0)