refactor: extract common integration test logic into abstract base classes (#473)

refactor: extract common integration test logic into abstract base classes

- Move duplicated test methods from WebFlux and WebMvc integration test classes to abstract base classes
- WebFluxSseIntegrationTests, WebFluxStreamableIntegrationTests now extend AbstractMcpClientServerIntegrationTests
- WebFluxStatelessIntegrationTests, WebMvcStatelessIntegrationTests now extend AbstractStatelessIntegrationTests  
- Each concrete test class now only implements transport-specific setup methods (prepareClients, prepareAsyncServerBuilder, prepareSyncServerBuilder)
- Eliminates ~1300+ lines of duplicated test code across multiple files
- Improves maintainability by centralizing test logic in reusable base classes
- Updates WebMvcSseServerTransportProvider to use builder pattern
- Adds new HttpServletSseIntegrationTests extending AbstractMcpClientServerIntegrationTests
  - Removes HttpServletSseServerTransportProviderIntegrationTests
- Standardizes timeout configurations and client setup across all integration tests

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Christian Tzolov 2025-08-08 21:31:29 +02:00 • committed by Christian Tzolov
commit dee8411c8f
3 changed files with 792 additions and 4785 deletions

View file

@ -42,11 +42,11 @@ class WebMvcSseIntegrationTests extends AbstractMcpClientServerIntegrationTests
clientBuilders.put("httpclient",
McpClient.sync(HttpClientSseClientTransport.builder("http://localhost:" + port).build())
.initializationTimeout(Duration.ofHours(10))
.requestTimeout(Duration.ofHours(10)));
clientBuilders.put("webflux", McpClient
.sync(WebFluxSseClientTransport.builder(WebClient.builder().baseUrl("http://localhost:" + port)).build()));
.sync(WebFluxSseClientTransport.builder(WebClient.builder().baseUrl("http://localhost:" + port)).build())
.requestTimeout(Duration.ofHours(10)));
}
@Configuration
@ -55,7 +55,10 @@ class WebMvcSseIntegrationTests extends AbstractMcpClientServerIntegrationTests
@Bean
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
return new WebMvcSseServerTransportProvider(new ObjectMapper(), MESSAGE_ENDPOINT);
return WebMvcSseServerTransportProvider.builder()
.objectMapper(new ObjectMapper())
.messageEndpoint(MESSAGE_ENDPOINT)
.build();
}
@Bean

View file

@ -11,8 +11,6 @@ import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@ -29,7 +27,6 @@ import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport
import io.modelcontextprotocol.server.McpServer.StatelessAsyncSpecification;
import io.modelcontextprotocol.server.McpServer.StatelessSyncSpecification;
import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport;
import io.modelcontextprotocol.spec.McpSchema;
import reactor.core.scheduler.Schedulers;
class WebMvcStatelessIntegrationTests extends AbstractStatelessIntegrationTests {
@ -63,6 +60,32 @@ class WebMvcStatelessIntegrationTests extends AbstractStatelessIntegrationTests
private TomcatTestUtil.TomcatServer tomcatServer;
@Override
protected StatelessAsyncSpecification prepareAsyncServerBuilder() {
return McpServer.async(this.mcpServerTransport);
}
@Override
protected StatelessSyncSpecification prepareSyncServerBuilder() {
return McpServer.sync(this.mcpServerTransport);
}
@Override
protected void prepareClients(int port, String mcpEndpoint) {
clientBuilders.put("httpclient", McpClient
.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + port).endpoint(mcpEndpoint).build())
.requestTimeout(Duration.ofHours(10)));
clientBuilders.put("webflux",
McpClient
.sync(WebClientStreamableHttpTransport
.builder(WebClient.builder().baseUrl("http://localhost:" + port))
.endpoint(mcpEndpoint)
.build())
.requestTimeout(Duration.ofHours(10)));
}
@BeforeEach
public void before() {
@ -76,33 +99,13 @@ class WebMvcStatelessIntegrationTests extends AbstractStatelessIntegrationTests
throw new RuntimeException("Failed to start Tomcat", e);
}
clientBuilders
.put("httpclient",
McpClient.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
.endpoint(MESSAGE_ENDPOINT)
.build()).initializationTimeout(Duration.ofHours(10)).requestTimeout(Duration.ofHours(10)));
clientBuilders.put("webflux",
McpClient.sync(WebClientStreamableHttpTransport
.builder(WebClient.builder().baseUrl("http://localhost:" + PORT))
.endpoint(MESSAGE_ENDPOINT)
.build()));
prepareClients(PORT, MESSAGE_ENDPOINT);
// Get the transport from Spring context
this.mcpServerTransport = tomcatServer.appContext().getBean(WebMvcStatelessServerTransport.class);
}
@Override
protected StatelessAsyncSpecification prepareAsyncServerBuilder() {
return McpServer.async(this.mcpServerTransport);
}
@Override
protected StatelessSyncSpecification prepareSyncServerBuilder() {
return McpServer.sync(this.mcpServerTransport);
}
@AfterEach
public void after() {
reactor.netty.http.HttpResources.disposeLoopsAndConnections();
@ -124,42 +127,4 @@ class WebMvcStatelessIntegrationTests extends AbstractStatelessIntegrationTests
}
}
@ParameterizedTest(name = "{0} : {displayName} ")
@ValueSource(strings = { "httpclient", "webflux" })
void simple(String clientType) {
var clientBuilder = clientBuilders.get(clientType);
var server = McpServer.async(this.mcpServerTransport)
.serverInfo("test-server", "1.0.0")
.requestTimeout(Duration.ofSeconds(1000))
.build();
try (
// Create client without sampling capabilities
var client = clientBuilder.clientInfo(new McpSchema.Implementation("Sample " + "client", "0.0.0"))
.requestTimeout(Duration.ofSeconds(1000))
.build()) {
assertThat(client.initialize()).isNotNull();
}
server.closeGracefully();
}
@Override
protected void prepareClients(int port, String mcpEndpoint) {
clientBuilders.put("httpclient", McpClient
.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + port).endpoint(mcpEndpoint).build())
.initializationTimeout(Duration.ofHours(10))
.requestTimeout(Duration.ofHours(10)));
clientBuilders.put("webflux",
McpClient.sync(WebClientStreamableHttpTransport
.builder(WebClient.builder().baseUrl("http://localhost:" + port))
.endpoint(mcpEndpoint)
.build()));
}
}

View file

@ -124,42 +124,20 @@ class WebMvcStreamableIntegrationTests extends AbstractMcpClientServerIntegratio
}
}
@ParameterizedTest(name = "{0} : {displayName} ")
@ValueSource(strings = { "httpclient", "webflux" })
void simple(String clientType) {
var clientBuilder = clientBuilders.get(clientType);
var server = McpServer.async(mcpServerTransportProvider)
.serverInfo("test-server", "1.0.0")
.requestTimeout(Duration.ofSeconds(1000))
.build();
try (
// Create client without sampling capabilities
var client = clientBuilder.clientInfo(new McpSchema.Implementation("Sample " + "client", "0.0.0"))
.requestTimeout(Duration.ofSeconds(1000))
.build()) {
assertThat(client.initialize()).isNotNull();
}
server.closeGracefully();
}
@Override
protected void prepareClients(int port, String mcpEndpoint) {
clientBuilders.put("httpclient", McpClient
.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + port).endpoint(mcpEndpoint).build())
.initializationTimeout(Duration.ofHours(10))
.requestTimeout(Duration.ofHours(10)));
clientBuilders.put("webflux",
McpClient.sync(WebClientStreamableHttpTransport
.builder(WebClient.builder().baseUrl("http://localhost:" + port))
.endpoint(mcpEndpoint)
.build()));
McpClient
.sync(WebClientStreamableHttpTransport
.builder(WebClient.builder().baseUrl("http://localhost:" + port))
.endpoint(mcpEndpoint)
.build())
.requestTimeout(Duration.ofHours(10)));
}
}