Use static container in all client and transport tests (#531)

- This allows reusing the containerized MCP server for all tests in a
  single class, significantly speeding up the tests, roughly 10x.

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2025-09-03 16:36:46 +02:00 • committed by GitHub
commit a5fd1ebff5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 69 additions and 119 deletions

View file

@ -4,6 +4,8 @@
package io.modelcontextprotocol.client;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.springframework.web.reactive.function.client.WebClient;
import org.testcontainers.containers.GenericContainer;
@ -19,7 +21,7 @@ public class WebClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncCli
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js streamableHttp")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -30,15 +32,15 @@ public class WebClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncCli
return WebClientStreamableHttpTransport.builder(WebClient.builder().baseUrl(host)).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
public void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}

View file

@ -4,6 +4,8 @@
package io.modelcontextprotocol.client;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.springframework.web.reactive.function.client.WebClient;
import org.testcontainers.containers.GenericContainer;
@ -19,7 +21,7 @@ public class WebClientStreamableHttpSyncClientTests extends AbstractMcpSyncClien
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js streamableHttp")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -30,15 +32,15 @@ public class WebClientStreamableHttpSyncClientTests extends AbstractMcpSyncClien
return WebClientStreamableHttpTransport.builder(WebClient.builder().baseUrl(host)).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
public void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}

View file

@ -6,6 +6,8 @@ package io.modelcontextprotocol.client;
import java.time.Duration;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.springframework.web.reactive.function.client.WebClient;
import org.testcontainers.containers.GenericContainer;
@ -26,7 +28,7 @@ class WebFluxSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js sse")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -37,15 +39,15 @@ class WebFluxSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
return WebFluxSseClientTransport.builder(WebClient.builder().baseUrl(host)).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
public void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}

View file

@ -8,6 +8,8 @@ import java.time.Duration;
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
import io.modelcontextprotocol.spec.McpClientTransport;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
@ -26,7 +28,7 @@ class WebFluxSseMcpSyncClientTests extends AbstractMcpSyncClientTests {
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js sse")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -37,15 +39,15 @@ class WebFluxSseMcpSyncClientTests extends AbstractMcpSyncClientTests {
return WebFluxSseClientTransport.builder(WebClient.builder().baseUrl(host)).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
protected void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}

View file

@ -13,7 +13,9 @@ import java.util.function.Function;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
@ -42,7 +44,7 @@ class WebFluxSseClientTransportTests {
static String host = "http://localhost:3001";
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js sse")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -95,15 +97,20 @@ class WebFluxSseClientTransportTests {
}
void startContainer() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@AfterAll
static void cleanup() {
container.stop();
}
@BeforeEach
void setUp() {
startContainer();
webClientBuilder = WebClient.builder().baseUrl(host);
objectMapper = new ObjectMapper();
transport = new TestSseClientTransport(webClientBuilder, objectMapper);
@ -115,11 +122,6 @@ class WebFluxSseClientTransportTests {
if (transport != null) {
assertThatCode(() -> transport.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
}
cleanup();
}
void cleanup() {
container.stop();
}
@Test

View file

@ -67,12 +67,6 @@ public abstract class AbstractMcpAsyncClientTests {
abstract protected McpClientTransport createMcpTransport();
protected void onStart() {
}
protected void onClose() {
}
protected Duration getRequestTimeout() {
return Duration.ofSeconds(14);
}
@ -117,16 +111,6 @@ public abstract class AbstractMcpAsyncClientTests {
}
}
@BeforeEach
void setUp() {
onStart();
}
@AfterEach
void tearDown() {
onClose();
}
<T> void verifyNotificationSucceedsWithImplicitInitialization(Function<McpAsyncClient, Mono<T>> operation,
String action) {
withClient(createMcpTransport(), mcpAsyncClient -> {

View file

@ -66,12 +66,6 @@ public abstract class AbstractMcpSyncClientTests {
abstract protected McpClientTransport createMcpTransport();
protected void onStart() {
}
protected void onClose() {
}
protected Duration getRequestTimeout() {
return Duration.ofSeconds(14);
}
@ -114,17 +108,6 @@ public abstract class AbstractMcpSyncClientTests {
}
}
@BeforeEach
void setUp() {
onStart();
}
@AfterEach
void tearDown() {
onClose();
}
static final Object DUMMY_RETURN_VALUE = new Object();
<T> void verifyNotificationSucceedsWithImplicitInitialization(Consumer<McpSyncClient> operation, String action) {

View file

@ -68,12 +68,6 @@ public abstract class AbstractMcpAsyncClientTests {
abstract protected McpClientTransport createMcpTransport();
protected void onStart() {
}
protected void onClose() {
}
protected Duration getRequestTimeout() {
return Duration.ofSeconds(14);
}
@ -118,16 +112,6 @@ public abstract class AbstractMcpAsyncClientTests {
}
}
@BeforeEach
void setUp() {
onStart();
}
@AfterEach
void tearDown() {
onClose();
}
<T> void verifyNotificationSucceedsWithImplicitInitialization(Function<McpAsyncClient, Mono<T>> operation,
String action) {
withClient(createMcpTransport(), mcpAsyncClient -> {

View file

@ -67,12 +67,6 @@ public abstract class AbstractMcpSyncClientTests {
abstract protected McpClientTransport createMcpTransport();
protected void onStart() {
}
protected void onClose() {
}
protected Duration getRequestTimeout() {
return Duration.ofSeconds(14);
}
@ -115,17 +109,6 @@ public abstract class AbstractMcpSyncClientTests {
}
}
@BeforeEach
void setUp() {
onStart();
}
@AfterEach
void tearDown() {
onClose();
}
static final Object DUMMY_RETURN_VALUE = new Object();
<T> void verifyNotificationSucceedsWithImplicitInitialization(Consumer<McpSyncClient> operation, String action) {

View file

@ -4,6 +4,8 @@
package io.modelcontextprotocol.client;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
@ -14,11 +16,11 @@ import io.modelcontextprotocol.spec.McpClientTransport;
@Timeout(15)
public class HttpClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncClientTests {
private String host = "http://localhost:3001";
private static String host = "http://localhost:3001";
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js streamableHttp")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -30,15 +32,15 @@ public class HttpClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncCl
return HttpClientStreamableHttpTransport.builder(host).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
public void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}

View file

@ -7,6 +7,8 @@ package io.modelcontextprotocol.client;
import java.net.URI;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.GenericContainer;
@ -17,7 +19,6 @@ import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequ
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.spec.McpClientTransport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@ -31,7 +32,7 @@ public class HttpClientStreamableHttpSyncClientTests extends AbstractMcpSyncClie
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js streamableHttp")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -44,15 +45,15 @@ public class HttpClientStreamableHttpSyncClientTests extends AbstractMcpSyncClie
return HttpClientStreamableHttpTransport.builder(host).httpRequestCustomizer(requestCustomizer).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
public void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}

View file

@ -4,6 +4,8 @@
package io.modelcontextprotocol.client;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
@ -19,11 +21,11 @@ import io.modelcontextprotocol.spec.McpClientTransport;
@Timeout(15)
class HttpSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
String host = "http://localhost:3004";
private static String host = "http://localhost:3004";
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js sse")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -34,15 +36,15 @@ class HttpSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
return HttpClientSseClientTransport.builder(host).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
protected void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}

View file

@ -7,6 +7,8 @@ package io.modelcontextprotocol.client;
import java.net.URI;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.GenericContainer;
@ -17,7 +19,6 @@ import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequ
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.spec.McpClientTransport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@ -33,11 +34,11 @@ import static org.mockito.Mockito.verify;
@Timeout(15) // Giving extra time beyond the client timeout
class HttpSseMcpSyncClientTests extends AbstractMcpSyncClientTests {
String host = "http://localhost:3003";
static String host = "http://localhost:3003";
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
@SuppressWarnings("resource")
GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2")
.withCommand("node dist/index.js sse")
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
.withExposedPorts(3001)
@ -50,15 +51,15 @@ class HttpSseMcpSyncClientTests extends AbstractMcpSyncClientTests {
return HttpClientSseClientTransport.builder(host).httpRequestCustomizer(requestCustomizer).build();
}
@Override
protected void onStart() {
@BeforeAll
static void startContainer() {
container.start();
int port = container.getMappedPort(3001);
host = "http://" + container.getHost() + ":" + port;
}
@Override
protected void onClose() {
@AfterAll
static void stopContainer() {
container.stop();
}