Skip to content

Commit 263e374

Browse files
Aliaksietzolov
authored andcommitted
feat(test): Use dynamic port allocation in integration tests (modelcontextprotocol#133)
- Add TestUtil class with findAvailablePort() method to the mcp-test module - Add findAvailablePort() method to TomcatTestUtil classes - Replace hardcoded port numbers with dynamic port allocation Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
1 parent 8068854 commit 263e374

File tree

12 files changed

+87
-29
lines changed

12 files changed

+87
-29
lines changed

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
1919
import io.modelcontextprotocol.server.McpServer;
2020
import io.modelcontextprotocol.server.McpServerFeatures;
21+
import io.modelcontextprotocol.server.TestUtil;
2122
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
2223
import io.modelcontextprotocol.spec.McpError;
2324
import io.modelcontextprotocol.spec.McpSchema;
@@ -50,9 +51,9 @@
5051
import static org.awaitility.Awaitility.await;
5152
import static org.mockito.Mockito.mock;
5253

53-
public class WebFluxSseIntegrationTests {
54+
class WebFluxSseIntegrationTests {
5455

55-
private static final int PORT = 8182;
56+
private static final int PORT = TestUtil.findAvailablePort();
5657

5758
private static final String CUSTOM_SSE_ENDPOINT = "/somePath/sse";
5859

@@ -133,7 +134,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) {
133134

134135
@ParameterizedTest(name = "{0} : {displayName} ")
135136
@ValueSource(strings = { "httpclient", "webflux" })
136-
void testCreateMessageSuccess(String clientType) throws InterruptedException {
137+
void testCreateMessageSuccess(String clientType) {
137138

138139
var clientBuilder = clientBuilders.get(clientType);
139140

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
@Timeout(15) // Giving extra time beyond the client timeout
2424
class WebFluxSseMcpAsyncServerTests extends AbstractMcpAsyncServerTests {
2525

26-
private static final int PORT = 8181;
26+
private static final int PORT = TestUtil.findAvailablePort();
2727

2828
private static final String MESSAGE_ENDPOINT = "/mcp/message";
2929

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
@Timeout(15) // Giving extra time beyond the client timeout
2424
class WebFluxSseMcpSyncServerTests extends AbstractMcpSyncServerTests {
2525

26-
private static final int PORT = 8182;
26+
private static final int PORT = TestUtil.findAvailablePort();
2727

2828
private static final String MESSAGE_ENDPOINT = "/mcp/message";
2929

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/TomcatTestUtil.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
*/
44
package io.modelcontextprotocol.server;
55

6+
import java.io.IOException;
7+
import java.net.InetSocketAddress;
8+
import java.net.ServerSocket;
9+
610
import org.apache.catalina.Context;
711
import org.apache.catalina.startup.Tomcat;
812

@@ -14,10 +18,14 @@
1418
*/
1519
public class TomcatTestUtil {
1620

21+
TomcatTestUtil() {
22+
// Prevent instantiation
23+
}
24+
1725
public record TomcatServer(Tomcat tomcat, AnnotationConfigWebApplicationContext appContext) {
1826
}
1927

20-
public TomcatServer createTomcatServer(String contextPath, int port, Class<?> componentClass) {
28+
public static TomcatServer createTomcatServer(String contextPath, int port, Class<?> componentClass) {
2129

2230
// Set up Tomcat first
2331
var tomcat = new Tomcat();

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class WebMvcSseAsyncServerTransportTests extends AbstractMcpAsyncServerTests {
2525

2626
private static final String MESSAGE_ENDPOINT = "/mcp/message";
2727

28-
private static final int PORT = 8181;
28+
private static final int PORT = TestUtil.findAvailablePort();
2929

3030
private Tomcat tomcat;
3131

@@ -73,7 +73,6 @@ protected McpServerTransportProvider createMcpTransportProvider() {
7373

7474
// Create DispatcherServlet with our Spring context
7575
DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
76-
// dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
7776

7877
// Add servlet to Tomcat and get the wrapper
7978
var wrapper = Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet);

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222

2323
import static org.assertj.core.api.Assertions.assertThat;
2424

25-
public class WebMvcSseCustomContextPathTests {
25+
class WebMvcSseCustomContextPathTests {
2626

2727
private static final String CUSTOM_CONTEXT_PATH = "/app/1";
2828

29-
private static final int PORT = 8183;
29+
private static final int PORT = TestUtil.findAvailablePort();
3030

3131
private static final String MESSAGE_ENDPOINT = "/mcp/message";
3232

@@ -39,11 +39,11 @@ public class WebMvcSseCustomContextPathTests {
3939
@BeforeEach
4040
public void before() {
4141

42-
tomcatServer = new TomcatTestUtil().createTomcatServer(CUSTOM_CONTEXT_PATH, PORT, TestConfig.class);
42+
tomcatServer = TomcatTestUtil.createTomcatServer(CUSTOM_CONTEXT_PATH, PORT, TestConfig.class);
4343

4444
try {
4545
tomcatServer.tomcat().start();
46-
assertThat(tomcatServer.tomcat().getServer().getState() == LifecycleState.STARTED);
46+
assertThat(tomcatServer.tomcat().getServer().getState()).isEqualTo(LifecycleState.STARTED);
4747
}
4848
catch (Exception e) {
4949
throw new RuntimeException("Failed to start Tomcat", e);

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646

4747
class WebMvcSseIntegrationTests {
4848

49-
private static final int PORT = 8183;
49+
private static final int PORT = TestUtil.findAvailablePort();
5050

5151
private static final String MESSAGE_ENDPOINT = "/mcp/message";
5252

@@ -75,7 +75,7 @@ public RouterFunction<ServerResponse> routerFunction(WebMvcSseServerTransportPro
7575
@BeforeEach
7676
public void before() {
7777

78-
tomcatServer = new TomcatTestUtil().createTomcatServer("", PORT, TestConfig.class);
78+
tomcatServer = TomcatTestUtil.createTomcatServer("", PORT, TestConfig.class);
7979

8080
try {
8181
tomcatServer.tomcat().start();
@@ -151,7 +151,7 @@ void testCreateMessageWithoutSamplingCapabilities() {
151151
}
152152

153153
@Test
154-
void testCreateMessageSuccess() throws InterruptedException {
154+
void testCreateMessageSuccess() {
155155

156156
Function<CreateMessageRequest, CreateMessageResult> samplingHandler = request -> {
157157
assertThat(request.messages()).hasSize(1);

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class WebMvcSseSyncServerTransportTests extends AbstractMcpSyncServerTests {
2424

2525
private static final String MESSAGE_ENDPOINT = "/mcp/message";
2626

27-
private static final int PORT = 8181;
27+
private static final int PORT = TestUtil.findAvailablePort();
2828

2929
private Tomcat tomcat;
3030

@@ -72,7 +72,6 @@ protected WebMvcSseServerTransportProvider createMcpTransportProvider() {
7272

7373
// Create DispatcherServlet with our Spring context
7474
DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
75-
// dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
7675

7776
// Add servlet to Tomcat and get the wrapper
7877
var wrapper = Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2025 - 2025 the original author or authors.
3+
*/
4+
package io.modelcontextprotocol.server;
5+
6+
import java.io.IOException;
7+
import java.net.InetSocketAddress;
8+
import java.net.ServerSocket;
9+
10+
public class TestUtil {
11+
12+
TestUtil() {
13+
// Prevent instantiation
14+
}
15+
16+
/**
17+
* Finds an available port on the local machine.
18+
* @return an available port number
19+
* @throws IllegalStateException if no available port can be found
20+
*/
21+
public static int findAvailablePort() {
22+
try (final ServerSocket socket = new ServerSocket()) {
23+
socket.bind(new InetSocketAddress(0));
24+
return socket.getLocalPort();
25+
}
26+
catch (final IOException e) {
27+
throw new IllegalStateException("Cannot bind to an available port!", e);
28+
}
29+
}
30+
31+
}

mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerCustomContextPathTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package io.modelcontextprotocol.server.transport;
55

66
import com.fasterxml.jackson.databind.ObjectMapper;
7+
78
import io.modelcontextprotocol.client.McpClient;
89
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
910
import io.modelcontextprotocol.server.McpServer;
@@ -17,9 +18,9 @@
1718

1819
import static org.assertj.core.api.Assertions.assertThat;
1920

20-
public class HttpServletSseServerCustomContextPathTests {
21+
class HttpServletSseServerCustomContextPathTests {
2122

22-
private static final int PORT = 8195;
23+
private static final int PORT = TomcatTestUtil.findAvailablePort();
2324

2425
private static final String CUSTOM_CONTEXT_PATH = "/api/v1";
2526

@@ -48,7 +49,7 @@ public void before() {
4849

4950
try {
5051
tomcat.start();
51-
assertThat(tomcat.getServer().getState() == LifecycleState.STARTED);
52+
assertThat(tomcat.getServer().getState()).isEqualTo(LifecycleState.STARTED);
5253
}
5354
catch (Exception e) {
5455
throw new RuntimeException("Failed to start Tomcat", e);

mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProviderIntegrationTests.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.stream.Collectors;
1313

1414
import com.fasterxml.jackson.databind.ObjectMapper;
15+
1516
import io.modelcontextprotocol.client.McpClient;
1617
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
1718
import io.modelcontextprotocol.server.McpServer;
@@ -44,9 +45,9 @@
4445
import static org.awaitility.Awaitility.await;
4546
import static org.mockito.Mockito.mock;
4647

47-
public class HttpServletSseServerTransportProviderIntegrationTests {
48+
class HttpServletSseServerTransportProviderIntegrationTests {
4849

49-
private static final int PORT = 8189;
50+
private static final int PORT = TomcatTestUtil.findAvailablePort();
5051

5152
private static final String CUSTOM_SSE_ENDPOINT = "/somePath/sse";
5253

@@ -70,7 +71,7 @@ public void before() {
7071
tomcat = TomcatTestUtil.createTomcatServer("", PORT, mcpServerTransportProvider);
7172
try {
7273
tomcat.start();
73-
assertThat(tomcat.getServer().getState() == LifecycleState.STARTED);
74+
assertThat(tomcat.getServer().getState()).isEqualTo(LifecycleState.STARTED);
7475
}
7576
catch (Exception e) {
7677
throw new RuntimeException("Failed to start Tomcat", e);
@@ -133,7 +134,7 @@ void testCreateMessageWithoutSamplingCapabilities() {
133134
}
134135

135136
@Test
136-
void testCreateMessageSuccess() throws InterruptedException {
137+
void testCreateMessageSuccess() {
137138

138139
Function<CreateMessageRequest, CreateMessageResult> samplingHandler = request -> {
139140
assertThat(request.messages()).hasSize(1);

mcp/src/test/java/io/modelcontextprotocol/server/transport/TomcatTestUtil.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
*/
44
package io.modelcontextprotocol.server.transport;
55

6-
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import java.io.IOException;
7+
import java.net.InetSocketAddress;
8+
import java.net.ServerSocket;
9+
710
import jakarta.servlet.Servlet;
811
import org.apache.catalina.Context;
9-
import org.apache.catalina.LifecycleState;
1012
import org.apache.catalina.startup.Tomcat;
1113

12-
import static org.junit.Assert.assertThat;
13-
1414
/**
1515
* @author Christian Tzolov
1616
*/
1717
public class TomcatTestUtil {
1818

19+
TomcatTestUtil() {
20+
// Prevent instantiation
21+
}
22+
1923
public static Tomcat createTomcatServer(String contextPath, int port, Servlet servlet) {
2024

2125
var tomcat = new Tomcat();
@@ -24,7 +28,6 @@ public static Tomcat createTomcatServer(String contextPath, int port, Servlet se
2428
String baseDir = System.getProperty("java.io.tmpdir");
2529
tomcat.setBaseDir(baseDir);
2630

27-
// Context context = tomcat.addContext("", baseDir);
2831
Context context = tomcat.addContext(contextPath, baseDir);
2932

3033
// Add transport servlet to Tomcat
@@ -42,4 +45,19 @@ public static Tomcat createTomcatServer(String contextPath, int port, Servlet se
4245
return tomcat;
4346
}
4447

48+
/**
49+
* Finds an available port on the local machine.
50+
* @return an available port number
51+
* @throws IllegalStateException if no available port can be found
52+
*/
53+
public static int findAvailablePort() {
54+
try (final ServerSocket socket = new ServerSocket()) {
55+
socket.bind(new InetSocketAddress(0));
56+
return socket.getLocalPort();
57+
}
58+
catch (final IOException e) {
59+
throw new IllegalStateException("Cannot bind to an available port!", e);
60+
}
61+
}
62+
4563
}

0 commit comments

Comments
 (0)