Skip to content

Commit e1cb892

Browse files
authored
Migrate removed LocalStack Service enum and getEndpointOverride in Testcontainers 2.x (#1014)
Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum and `getEndpointOverride(...)`. The existing `Testcontainers2Migration` only renamed the type, leaving `LocalStackContainer.Service.SQS` and `getEndpointOverride(...)` as unresolved references (Java and Kotlin alike). Add a `Testcontainers2LocalStack` step (run before the type rename) that replaces `Service` constants with their service-name strings and `getEndpointOverride(service)` with `getEndpoint()`.
1 parent 3934d1e commit e1cb892

3 files changed

Lines changed: 369 additions & 0 deletions

File tree

src/main/resources/META-INF/rewrite/recipes.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
240240
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.ExplicitContainerImages,Explicit container images and versions,Replace implicit default container images and versions with explicit versions.,29,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
241241
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.Testcontainers2Dependencies,Rename Testcontainers dependencies,Change Testcontainers dependencies to adopt the new consistent `testcontainers-` prefix.,63,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
242242
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.Testcontainers2ContainerClasses,Testcontainers 2 container classes,Change Testcontainers container classes to their new package locations in Testcontainers 2.x.,54,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
243+
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.Testcontainers2LocalStack,Migrate removed `LocalStackContainer` members to Testcontainers 2.x,"Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum and the `getEndpointOverride(...)` method. Replace `LocalStackContainer.Service` constants with the equivalent service name strings and `getEndpointOverride(service)` with `getEndpoint()`, so code continues to compile against Testcontainers 2.x. This runs while the type is still `org.testcontainers.containers.localstack.LocalStackContainer`, before it is renamed.",4,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
243244
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testng.TestNgAssertEqualsToAssertThat,TestNG `assertEquals` to AssertJ,Convert TestNG-style `assertEquals()` to AssertJ's `assertThat().isEqualTo()`.,1,TestNG,Testing,Java,Recipes for migrating from [TestNG](https://testng.org/) to JUnit.,,Basic building blocks for transforming Java code.,,
244245
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testng.TestNgAssertNotEqualsToAssertThat,TestNG `assertNotEquals` to AssertJ,Convert TestNG-style `assertNotEquals()` to AssertJ's `assertThat().isNotEqualTo()`.,1,TestNG,Testing,Java,Recipes for migrating from [TestNG](https://testng.org/) to JUnit.,,Basic building blocks for transforming Java code.,,
245246
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testng.TestNgToAssertj,Migrate TestNG assertions to AssertJ,Convert assertions from `org.testng.Assert` to `org.assertj.core.api.Assertions`.,48,TestNG,Testing,Java,Recipes for migrating from [TestNG](https://testng.org/) to JUnit.,,Basic building blocks for transforming Java code.,,

src/main/resources/META-INF/rewrite/testcontainers.yml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,119 @@ recipeList:
7878
oldFullyQualifiedTypeName: org.testcontainers.containers.DockerComposeContainer
7979
newFullyQualifiedTypeName: org.testcontainers.containers.ComposeContainer
8080
- org.openrewrite.java.testing.testcontainers.Testcontainers2Dependencies
81+
- org.openrewrite.java.testing.testcontainers.Testcontainers2LocalStack
8182
- org.openrewrite.java.testing.testcontainers.Testcontainers2ContainerClasses
8283
---
8384
type: specs.openrewrite.org/v1beta/recipe
85+
name: org.openrewrite.java.testing.testcontainers.Testcontainers2LocalStack
86+
displayName: Migrate removed `LocalStackContainer` members to Testcontainers 2.x
87+
description: >-
88+
Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum and the
89+
`getEndpointOverride(...)` method. Replace `LocalStackContainer.Service` constants with the
90+
equivalent service name strings and `getEndpointOverride(service)` with `getEndpoint()`, so code
91+
continues to compile against Testcontainers 2.x. This runs while the type is still
92+
`org.testcontainers.containers.localstack.LocalStackContainer`, before it is renamed.
93+
preconditions:
94+
- org.openrewrite.Singleton
95+
recipeList:
96+
# Testcontainers 2.x removed the `LocalStackContainer.Service` enum; replace each constant with its service name string.
97+
- org.openrewrite.java.ReplaceConstant:
98+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
99+
constantName: API_GATEWAY
100+
literalValue: '"apigateway"'
101+
- org.openrewrite.java.ReplaceConstant:
102+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
103+
constantName: EC2
104+
literalValue: '"ec2"'
105+
- org.openrewrite.java.ReplaceConstant:
106+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
107+
constantName: KINESIS
108+
literalValue: '"kinesis"'
109+
- org.openrewrite.java.ReplaceConstant:
110+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
111+
constantName: DYNAMODB
112+
literalValue: '"dynamodb"'
113+
- org.openrewrite.java.ReplaceConstant:
114+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
115+
constantName: DYNAMODB_STREAMS
116+
literalValue: '"dynamodbstreams"'
117+
- org.openrewrite.java.ReplaceConstant:
118+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
119+
constantName: S3
120+
literalValue: '"s3"'
121+
- org.openrewrite.java.ReplaceConstant:
122+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
123+
constantName: FIREHOSE
124+
literalValue: '"firehose"'
125+
- org.openrewrite.java.ReplaceConstant:
126+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
127+
constantName: LAMBDA
128+
literalValue: '"lambda"'
129+
- org.openrewrite.java.ReplaceConstant:
130+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
131+
constantName: SNS
132+
literalValue: '"sns"'
133+
- org.openrewrite.java.ReplaceConstant:
134+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
135+
constantName: SQS
136+
literalValue: '"sqs"'
137+
- org.openrewrite.java.ReplaceConstant:
138+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
139+
constantName: REDSHIFT
140+
literalValue: '"redshift"'
141+
- org.openrewrite.java.ReplaceConstant:
142+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
143+
constantName: SES
144+
literalValue: '"ses"'
145+
- org.openrewrite.java.ReplaceConstant:
146+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
147+
constantName: ROUTE53
148+
literalValue: '"route53"'
149+
- org.openrewrite.java.ReplaceConstant:
150+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
151+
constantName: CLOUDFORMATION
152+
literalValue: '"cloudformation"'
153+
- org.openrewrite.java.ReplaceConstant:
154+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
155+
constantName: CLOUDWATCH
156+
literalValue: '"cloudwatch"'
157+
- org.openrewrite.java.ReplaceConstant:
158+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
159+
constantName: SSM
160+
literalValue: '"ssm"'
161+
- org.openrewrite.java.ReplaceConstant:
162+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
163+
constantName: SECRETSMANAGER
164+
literalValue: '"secretsmanager"'
165+
- org.openrewrite.java.ReplaceConstant:
166+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
167+
constantName: STEPFUNCTIONS
168+
literalValue: '"stepfunctions"'
169+
- org.openrewrite.java.ReplaceConstant:
170+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
171+
constantName: CLOUDWATCHLOGS
172+
literalValue: '"logs"'
173+
- org.openrewrite.java.ReplaceConstant:
174+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
175+
constantName: STS
176+
literalValue: '"sts"'
177+
- org.openrewrite.java.ReplaceConstant:
178+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
179+
constantName: IAM
180+
literalValue: '"iam"'
181+
- org.openrewrite.java.ReplaceConstant:
182+
owningType: org.testcontainers.containers.localstack.LocalStackContainer$Service
183+
constantName: KMS
184+
literalValue: '"kms"'
185+
# Testcontainers 2.x removed `getEndpointOverride(Service)`; all services share the single `getEndpoint()`.
186+
- org.openrewrite.java.ChangeMethodName:
187+
methodPattern: org.testcontainers.containers.localstack.LocalStackContainer getEndpointOverride(..)
188+
newMethodName: getEndpoint
189+
- org.openrewrite.java.DeleteMethodArgument:
190+
methodPattern: org.testcontainers.containers.localstack.LocalStackContainer getEndpoint(..)
191+
argumentIndex: 0
192+
---
193+
type: specs.openrewrite.org/v1beta/recipe
84194
name: org.openrewrite.java.testing.testcontainers.GetHostMigration
85195
displayName: Replace `ContainerState.getContainerIpAddress()` with `getHost()`
86196
description: Replace `org.testcontainers.containers.ContainerState.getContainerIpAddress()` with `getHost()`.
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.testcontainers;
17+
18+
import org.junit.jupiter.api.Nested;
19+
import org.junit.jupiter.api.Test;
20+
import org.openrewrite.DocumentExample;
21+
import org.openrewrite.InMemoryExecutionContext;
22+
import org.openrewrite.java.JavaParser;
23+
import org.openrewrite.kotlin.KotlinParser;
24+
import org.openrewrite.test.RecipeSpec;
25+
import org.openrewrite.test.RewriteTest;
26+
27+
import static org.openrewrite.java.Assertions.java;
28+
import static org.openrewrite.kotlin.Assertions.kotlin;
29+
30+
class Testcontainers2LocalStackTest implements RewriteTest {
31+
32+
@Override
33+
public void defaults(RecipeSpec spec) {
34+
spec
35+
.recipeFromResources("org.openrewrite.java.testing.testcontainers.Testcontainers2LocalStack")
36+
.parser(JavaParser.fromJavaVersion()
37+
.classpathFromResources(new InMemoryExecutionContext(), "testcontainers-localstack", "testcontainers-2"))
38+
.parser(KotlinParser.builder()
39+
.classpathFromResources(new InMemoryExecutionContext(), "testcontainers-localstack", "testcontainers-2"));
40+
}
41+
42+
@DocumentExample
43+
@Test
44+
void getEndpointOverrideToGetEndpoint() {
45+
rewriteRun(
46+
//language=java
47+
java(
48+
"""
49+
import org.testcontainers.containers.localstack.LocalStackContainer;
50+
51+
import java.net.URI;
52+
53+
class A {
54+
URI endpoint(LocalStackContainer localStackContainer) {
55+
return localStackContainer.getEndpointOverride(LocalStackContainer.Service.SQS);
56+
}
57+
}
58+
""",
59+
"""
60+
import org.testcontainers.containers.localstack.LocalStackContainer;
61+
62+
import java.net.URI;
63+
64+
class A {
65+
URI endpoint(LocalStackContainer localStackContainer) {
66+
return localStackContainer.getEndpoint();
67+
}
68+
}
69+
"""
70+
)
71+
);
72+
}
73+
74+
@Test
75+
void getEndpointOverrideToGetEndpointKotlin() {
76+
rewriteRun(
77+
//language=kotlin
78+
kotlin(
79+
"""
80+
import org.testcontainers.containers.localstack.LocalStackContainer
81+
82+
class A {
83+
fun endpoint(localStackContainer: LocalStackContainer) =
84+
localStackContainer.getEndpointOverride(LocalStackContainer.Service.SQS)
85+
}
86+
""",
87+
"""
88+
import org.testcontainers.containers.localstack.LocalStackContainer
89+
90+
class A {
91+
fun endpoint(localStackContainer: LocalStackContainer) =
92+
localStackContainer.getEndpoint()
93+
}
94+
"""
95+
)
96+
);
97+
}
98+
99+
@Test
100+
void servicesAndEndpointTogether() {
101+
rewriteRun(
102+
//language=java
103+
java(
104+
"""
105+
import org.testcontainers.containers.localstack.LocalStackContainer;
106+
107+
import java.net.URI;
108+
109+
class A {
110+
LocalStackContainer localstack = new LocalStackContainer()
111+
.withServices(LocalStackContainer.Service.SQS);
112+
113+
URI endpoint() {
114+
return localstack.getEndpointOverride(LocalStackContainer.Service.SQS);
115+
}
116+
}
117+
""",
118+
"""
119+
import org.testcontainers.containers.localstack.LocalStackContainer;
120+
121+
import java.net.URI;
122+
123+
class A {
124+
LocalStackContainer localstack = new LocalStackContainer()
125+
.withServices("sqs");
126+
127+
URI endpoint() {
128+
return localstack.getEndpoint();
129+
}
130+
}
131+
"""
132+
)
133+
);
134+
}
135+
136+
@Test
137+
void nonTrivialServiceNames() {
138+
rewriteRun(
139+
//language=java
140+
java(
141+
"""
142+
import org.testcontainers.containers.localstack.LocalStackContainer;
143+
144+
class A {
145+
LocalStackContainer localstack = new LocalStackContainer()
146+
.withServices(LocalStackContainer.Service.API_GATEWAY,
147+
LocalStackContainer.Service.DYNAMODB_STREAMS,
148+
LocalStackContainer.Service.CLOUDWATCHLOGS);
149+
}
150+
""",
151+
"""
152+
import org.testcontainers.containers.localstack.LocalStackContainer;
153+
154+
class A {
155+
LocalStackContainer localstack = new LocalStackContainer()
156+
.withServices("apigateway",
157+
"dynamodbstreams",
158+
"logs");
159+
}
160+
"""
161+
)
162+
);
163+
}
164+
165+
@Test
166+
void withServicesKotlin() {
167+
rewriteRun(
168+
//language=kotlin
169+
kotlin(
170+
"""
171+
import org.testcontainers.containers.localstack.LocalStackContainer
172+
173+
class A {
174+
val localstack: LocalStackContainer = LocalStackContainer()
175+
.withServices(LocalStackContainer.Service.SQS, LocalStackContainer.Service.S3)
176+
}
177+
""",
178+
"""
179+
import org.testcontainers.containers.localstack.LocalStackContainer
180+
181+
class A {
182+
val localstack: LocalStackContainer = LocalStackContainer()
183+
.withServices("sqs", "s3")
184+
}
185+
"""
186+
)
187+
);
188+
}
189+
190+
/**
191+
* End-to-end reproduction of the customer-reported Kotlin files (issue #2437) run through the full
192+
* {@code Testcontainers2Migration}, asserting the LocalStack member references are migrated alongside
193+
* the type rename so the result compiles against Testcontainers 2.x.
194+
*/
195+
@Nested
196+
class FullMigration implements RewriteTest {
197+
198+
@Override
199+
public void defaults(RecipeSpec spec) {
200+
spec
201+
.recipeFromResources("org.openrewrite.java.testing.testcontainers.Testcontainers2Migration")
202+
.parser(KotlinParser.builder()
203+
.classpathFromResources(new InMemoryExecutionContext(), "testcontainers-localstack", "testcontainers-2"));
204+
}
205+
206+
@Test
207+
void awsConfig() {
208+
rewriteRun(
209+
//language=kotlin
210+
kotlin(
211+
"""
212+
import org.testcontainers.containers.localstack.LocalStackContainer
213+
214+
class AwsConfig {
215+
fun endpoint(localStackContainer: LocalStackContainer) =
216+
localStackContainer.getEndpointOverride(LocalStackContainer.Service.SQS)
217+
}
218+
""",
219+
"""
220+
import org.testcontainers.localstack.LocalStackContainer
221+
222+
class AwsConfig {
223+
fun endpoint(localStackContainer: LocalStackContainer) =
224+
localStackContainer.getEndpoint()
225+
}
226+
"""
227+
)
228+
);
229+
}
230+
231+
@Test
232+
void baseTestcontainersConfig() {
233+
rewriteRun(
234+
//language=kotlin
235+
kotlin(
236+
"""
237+
import org.testcontainers.containers.localstack.LocalStackContainer
238+
import org.testcontainers.utility.DockerImageName
239+
240+
class BaseTestcontainersConfig {
241+
val localStackContainer: LocalStackContainer = LocalStackContainer(DockerImageName.parse("localstack/localstack:3.4.0"))
242+
.withServices(LocalStackContainer.Service.SQS)
243+
}
244+
""",
245+
"""
246+
import org.testcontainers.localstack.LocalStackContainer
247+
import org.testcontainers.utility.DockerImageName
248+
249+
class BaseTestcontainersConfig {
250+
val localStackContainer: LocalStackContainer = LocalStackContainer(DockerImageName.parse("localstack/localstack:3.4.0"))
251+
.withServices("sqs")
252+
}
253+
"""
254+
)
255+
);
256+
}
257+
}
258+
}

0 commit comments

Comments
 (0)