Spring Boot 3.1 新しい ConnectionDetails と ServiceConnection 抽象化を追加することで 、Testcontainers のサポートを導入しました。 まだ読んでいない場合は、 Spring Boot Application Testing and Development with Testcontainersを確認してください。
次の Testcontainers コンテナの抽象化は、Spring Boot 3の一部としてすでにサポートされています。1:
- Cassandraコンテナ
- カウチベースコンテナ
- エラスティックサーチコンテナ
- Redisまたはopenzipkin/zipkinを使用したGenericContainer
- JDBC データベースコンテナ
- カフカコンテナ
- MongoDBコンテナ
- MariaDBコンテナー
- MSSQLサーバーコンテナ
- MySQLコンテナ
- ネオ4jContainer
- オラクルコンテナ
- PostgreSQLコンテナ
- RabbitMQコンテナ
- レッドパンダコンテナ
Spring Boot 3.2。0 楨:
- GenericContainer (symptoma/activemq または otel/opentelemetry-collector-contrib を使用)
- OracleContainer (オラクル・フリー)
- パルサーコンテナ
詳細については、Spring Boot 320の新機能をご覧ください。
Testcontainers を使用すると、統合テストを作成し、開発中のアプリケーションと対話するサービスとの間の統合が意図したとおりに機能することを確信できます。 WireMock は、APIの依存関係をシミュレートすることで役立ちます。
このコンテキストでは、API 依存関係とは、アプリケーションが特定の機能またはデータに依存している外部サービスを指します。 この例では、API 依存関係として GitHub API を使用します。 このような依存関係を自分で実行することは、多くの場合、簡単ではありません。 現在、WireMockは独自のWireMock実装を提供しています。
独自の Spring Boot自動設定 を構築することは、チーム間で共有できる統合を実装する場合の一般的なユースケースです。 次の例では、GitHub GraphQL API と統合するための簡単な自動構成を記述します。
まず、WireMockのTestcontainers依存関係を宣言しましょう。
<dependency>
<groupId>org.wiremock.integrations.testcontainers</groupId>
<artifactId>wiremock-testcontainers-module</artifactId>
<version>1.0-alpha-13</version>
</dependency>
また、 maven-dependency-plugin
を追加しましょう。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>compile</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.github.nilwurtz</groupId>
<artifactId>wiremock-graphql-extension</artifactId>
<version>0.7.1</version>
<classifier>jar-with-dependencies</classifier>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/test-wiremock-extension</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
プラグインは、テストの記述時に使用されるWireMock GraphQL Extensionをダウンロードします。
自動構成の構築
現在、次のアプローチは、アプリケーションにカスタム構成オプションを提供するための推奨される方法です。 @ConfigurationProperties
を使用すると、環境変数、システムプロパティ、またはapplication.properties
ファイルやapplication.yaml
ファイルのプロパティを介してgithub.url
とgithub.token
の値を注入できるため、Springの組み込み構成機能を最大限に活用できます。
@ConfigurationProperties(prefix = "github")
public record GHProperties(String url, String token) {
}
GHProperties
Bean は、以下の AutoConfiguration で確認できるように、@EnableConfigurationProperties
を使用して必要な場所に注入できます。
@AutoConfiguration
@EnableConfigurationProperties(GHProperties.class)
public class GHAutoConfiguration {
@Bean
GraphQlClient ghGraphQlClient(GHProperties properties, WebClient.Builder webClientBuilder) {
var githubBaseUrl = properties.url();
var authorizationHeader = "Bearer %s".formatted(properties.token());
return HttpGraphQlClient
.builder(webClientBuilder.build())
.url(githubBaseUrl + "/graphql")
.header("Authorization", authorizationHeader)
.build();
}
}
次に、 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
に自動構成を登録しましょう。
com.example.testcontainerswiremockexample.GHAutoConfiguration
GHAutoConfiguration
インフラストラクチャを提供し、個別にパッケージ化および配布できます。 その後、JARは任意のプロジェクトで使用でき、アプリケーションの一部として使用できます。
自動設定の使用
GHService
クラスを作成してGraphQlClient
を使用してみましょう。これは、以前に実装された自動設定のおかげで自動的に注入されます。
@Service
public class GHService {
private final GraphQlClient graphQlClient;
public GHService(GraphQlClient ghGraphQlClient) {
this.ghGraphQlClient = ghGraphQlClient;
}
public Mono<GitHubResponse> getStats(Map<String, Object> variables) {
return this.graphQlClient.documentName("githubStats")
.operationName("Stats")
.variables(variables)
.retrieve("repository")
.toEntity(GitHubResponse.class);
}
}
GraphQL クエリは、githubStats.graphql
また、この操作は GitHubResponse
エンティティを返します。
public record GitHubResponse(Issues issues, PullRequests pullRequests, Stargazers stargazers, Watchers watchers, Forks forks) {
record Issues(int totalCount) {}
record PullRequests(int totalCount) {}
record Stargazers(int totalCount) {}
record Watchers(int totalCount) {}
record Forks(int totalCount) {}
}
次に、以下のコンテンツを src/main/java/graphql-documents/githubStats.graphql
で追加しましょう。
query Stats($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
issues(states: OPEN) {
totalCount
}
pullRequests(states: OPEN) {
totalCount
}
stargazers {
totalCount
} watchers {
totalCount
} forks {
totalCount
}
}
}
@DynamicPropertySourceによるテスト
@DynamicPropertySource
のおかげで、実行時にGHProperties
で定義されたプロパティを指定できるため、テストには、Testcontainersを介して実行されるWireMockインスタンスに接続するための適切な構成が含まれます。
@SpringBootTest
@Testcontainers
class TestcontainersWiremockExampleApplicationTests {
private static final Logger LOGGER = LoggerFactory.getLogger("wiremock");
@Container
static WireMockContainer wireMock = new WireMockContainer("wiremock/wiremock:3.2.0-alpine")
.withMapping("graphql", TestcontainersWiremockExampleApplicationTests.class, "graphql-resource.json")
.withExtensions("graphql",
List.of("io.github.nilwurtz.GraphqlBodyMatcher"),
List.of(Paths.get("target", "test-wiremock-extension", "wiremock-graphql-extension-0.7.1-jar-with-dependencies.jar").toFile()))
.withFileFromResource("testcontainers-java.json", TestcontainersWiremockExampleApplicationTests.class, "testcontainers-java.json")
.withLogConsumer(new Slf4jLogConsumer(LOGGER));
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("github.url", wireMock::getBaseUrl);
registry.add("github.token", () -> "test");
}
@Autowired
private GHService ghService;
@Test
void contextLoads() {
var variables = Map.<String, Object>of("owner", "testcontainers", "name", "testcontainers-java");
StepVerifier.create(this.ghService.getStats(variables))
.expectNext(new GitHubResponse(
new GitHubResponse.Issues(385),
new GitHubResponse.PullRequests(90),
new GitHubResponse.Stargazers(6560),
new GitHubResponse.Watchers(142),
new GitHubResponse.Forks(1295)))
.verifyComplete();
}
}
このテストでは、マッピング、拡張機能、および応答と共に WireMockContainer
を定義します。 また、リクエストが一致しない場合にWireMockからフィードバックを取得するために、ログを設定します。
統合テストには、追加のリソースも必要です src/test/resources/com/example/testcontainerswiremockexamples/TestcontainersWiremockExampleApplicationTests/graphql-resource.json
:
{
"request": {
"method": "POST",
"url": "/graphql",
"headers": {
"Authorization": {
"contains": "Bearer"
}
},
"bodyPatterns": [
{
"equalToJson": "{\"query\":\"query Stats($owner: String!, $name: String!) {\\n repository(owner: $owner, name: $name) {\\n issues(states: OPEN) {\\n totalCount\\n }\\n pullRequests(states: OPEN) {\\n totalCount\\n }\\n stargazers {\\n totalCount\\n } watchers {\\n totalCount\\n } forks {\\n totalCount\\n }\\n }\\n}\", \"operationName\": \"Stats\", \"variables\":{\"owner\":\"testcontainers\",\"name\":\"testcontainers-java\"}}"
}
]
},
"response": {
"status": 200,
"bodyFileName": "testcontainers-java.json",
"headers": {
"Content-Type": "application/json"
}
}
}
そして、 src/test/resources/com/example/testcontainerswiremockexamples/TestcontainersWiremockExampleApplicationTests/testcontainers-java.json
:
{
"data": {
"repository": {
"issues": {
"totalCount": 385
},
"pullRequests": {
"totalCount": 90
},
"stargazers": {
"totalCount": 6560
},
"watchers": {
"totalCount": 142
},
"forks": {
"totalCount": 1295
}
}
}
}
ConnectionDetails サポートの追加
ConnectionDetails
を追加して、自動設定を最新化しましょう。この場合、 GHProperties
と同じパラメータが公開されています。これは、WireMockでシミュレーションしているサービスに接続するために必要だからです。
public interface GHConnectionDetails extends ConnectionDetails {
String url();
String token();
}
GHConnectionsDetails
は 2 つの実装を提供します。最初のものは引き続きプロパティに依存し、2番目のものは後で示します。
class PropertiesGHConnectionDetails implements GHConnectionDetails {
private final GHProperties properties;
public PropertiesGHConnectionDetails(GHProperties properties) {
this.properties = properties;
}
@Override
public String url() {
return this.properties.url();
}
@Override
public String token() {
return this.properties.token();
}
}
次に、自動設定の一部として、他の Bean が提供されていない場合に GHConnectionDetails
Bean が作成されます。 PropertiesGHConnectionDetails
は、他に提供されていない場合のデフォルトの実装になります。
@AutoConfiguration
@EnableConfigurationProperties(GHProperties.class)
public class GHAutoConfiguration {
@Bean
@ConditionalOnMissingBean(GHConnectionDetails.class)
GHConnectionDetails ghConnectionDetails(GHProperties properties) {
return new PropertiesGHConnectionDetails(properties);
}
@Bean
GraphQlClient ghGraphQlClient(GHConnectionDetails connectionDetails, WebClient.Builder webClientBuilder) {
var githubBaseUrl = connectionDetails.url();
var authorizationHeader = "Bearer %s".formatted(connectionDetails.token());
return HttpGraphQlClient
.builder(webClientBuilder.build())
.url(githubBaseUrl + "/graphql")
.header("Authorization", authorizationHeader)
.build();
}
}
Spring BootのServiceConnectionにWireMock Testcontainersを追加する
続行する前に、 spring-boot-testcontainers
依存関係として宣言しましょう。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
</dependency>
この時点では、実装は 1 つだけで、 PropertiesGHConnectionDetails
です。 しかし、WireMockContainer
に依存する GHConnectionDetails
の 2 番目の実装を追加しましょう。
class WireMockContainerConnectionDetailsFactory extends ContainerConnectionDetailsFactory<WireMockContainer, GHConnectionDetails> {
WireMockContainerConnectionDetailsFactory() {
}
protected GHConnectionDetails getContainerConnectionDetails(ContainerConnectionSource<WireMockContainer> source) {
return new WireMockContainerConnectionDetails(source);
}
private static final class WireMockContainerConnectionDetails extends ContainerConnectionDetailsFactory.ContainerConnectionDetails<WireMockContainer> implements GHConnectionDetails {
private WireMockContainerConnectionDetails(ContainerConnectionSource<WireMockContainer> source) {
super(source);
}
@Override
public String url() {
return getContainer().getBaseUrl();
}
@Override
public String token() {
return "test-token";
}
}
}
src/main/resources/META-INF/spring.factories
でWireMockContainerConnectionDetailsFactory
を登録しましょう。
org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\
com.example.testcontainerswiremockexample.WireMockContainerConnectionDetailsFactor
WireMock Testcontainers ServiceConnectionの使用
これで、 WireMockContainer
のプロパティの手動マッピングを削除し、 @ServiceConnection
に直接依存できます。
@SpringBootTest
@Testcontainers
class TestcontainersWiremockExampleApplicationTests {
private static final Logger LOGGER = LoggerFactory.getLogger("wiremock");
@Container
@ServiceConnection
static WireMockContainer wireMock = new WireMockContainer("wiremock/wiremock:3.2.0-alpine")
.withMapping("graphql", TestcontainersWiremockExampleApplicationTests.class, "graphql-resource.json")
.withExtensions("graphql",
List.of("io.github.nilwurtz.GraphqlBodyMatcher"),
List.of(Paths.get("target", "test-wiremock-extension", "wiremock-graphql-extension-0.7.1-jar-with-dependencies.jar").toFile()))
.withFileFromResource("testcontainers-java.json", TestcontainersWiremockExampleApplicationTests.class, "testcontainers-java.json")
.withLogConsumer(new Slf4jLogConsumer(LOGGER));
@Autowired
private GHService ghService;
@Test
void contextLoads() {
var variables = Map.<String, Object>of("owner", "testcontainers", "name", "testcontainers-java");
StepVerifier.create(this.ghService.getStats(variables))
.expectNext(new GitHubResponse(
new GitHubResponse.Issues(385),
new GitHubResponse.PullRequests(90),
new GitHubResponse.Stargazers(6560),
new GitHubResponse.Watchers(142),
new GitHubResponse.Forks(1295)))
.verifyComplete();
}
}
そして、そのように、テストまたは開発中にこれらの外部サービスに接続するためのサポートを Testcontainers によって実現できます。 ソースコードは GitHubにあります。
結論
TestcontainersとWireMockを使用してテストおよび開発中にAPIの動作をシミュレートすることは、ソフトウェア開発チームに多くのメリットを提供する強力で効率的なアプローチです。
この組み合わせにより、エンジニアリングチームは、実際のAPIインタラクションを厳密に反映した制御された分離されたテスト環境を作成し、堅牢で信頼性の高いテストプラクティスを促進できます。 また、Spring Boot の ConnectionDetails と ServiceConnection の抽象化により、Testcontainers の統合と開発者エクスペリエンスはシームレスになります。
さらに詳しく
- Docker Newsletter を購読してください。
- Docker デスクトップの最新リリースを入手します。
- ドッカーは初めてですか? 始めましょう。
- Testcontainersについて質問がありますか? Testcontainers Slackで接続します。
- 無料アカウントを作成して、Testcontainers Cloud の使用を開始してください。
- Testcontainers のベスト プラクティスについて説明します。
- Testcontainers ガイドから開始します。