如何在不使用MockMvc时结合SpringJUnit4ClassRunner进行Jetty WebSocket测试?
我之前也碰到过这个Jetty+Spring WebSocket集成测试的棘手问题,核心原因确实是Spring的MockServletContext没实现Jetty要求的Context接口,导致依赖MockMvc的测试方式完全行不通。不过不用慌,我们可以直接启动真实的Jetty容器来配合SpringJUnit4ClassRunner做测试,具体方案如下:
1. 搭建测试类基础结构
你的测试类依然可以保留SpringJUnit4ClassRunner和@ContextConfiguration来加载Spring上下文,但必须去掉@WebAppConfiguration——这个注解会触发Spring创建MockServletContext,而这正是导致兼容性问题的根源。
接下来,我们要在测试类里手动初始化、启动Jetty容器,并把Spring的DispatcherServlet和WebSocket组件注册进去。示例代码如下:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {ApplicationConfig.class, WebSocketConfig.class}) public class JettyWebSocketIntegrationTest { private Server jettyServer; private final String baseUrl = "http://localhost:8080"; @Autowired private ApplicationContext applicationContext; @Before public void startJetty() throws Exception { // 初始化Jetty服务器,指定端口 jettyServer = new Server(8080); // 创建Servlet上下文处理器 ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); contextHandler.setContextPath("/"); jettyServer.setHandler(contextHandler); // 注册Spring的DispatcherServlet,关联已加载的Spring上下文 ServletHolder dispatcherHolder = new ServletHolder(new DispatcherServlet(applicationContext)); contextHandler.addServlet(dispatcherHolder, "/*"); // 注册Jetty WebSocket处理器,关联你的WebSocket端点类 WebSocketHandler wsHandler = new WebSocketHandler() { @Override public void configure(WebSocketServletFactory factory) { // 替换成你自己的WebSocket端点类(比如带@ServerEndpoint注解的类) factory.register(YourWebSocketEndpoint.class); } }; // 指定WebSocket的访问路径 contextHandler.addHandler(wsHandler, "/ws/**"); // 启动Jetty jettyServer.start(); } @After public void stopJetty() throws Exception { // 测试结束后停止并销毁Jetty,避免端口占用 if (jettyServer != null) { jettyServer.stop(); jettyServer.destroy(); } } // 测试方法示例:用真实WebSocket客户端验证功能 @Test public void testWebSocketMessageExchange() throws Exception { // 初始化Jetty的WebSocket客户端 WebSocketClient client = new WebSocketClient(); client.start(); // 创建自定义的WebSocket监听器,用于接收服务端响应 TestWebSocketListener listener = new TestWebSocketListener(); // 连接到服务端的WebSocket端点 Session session = client.connect(listener, new URI(baseUrl + "/ws/test-endpoint")).get(); // 发送测试消息 session.getRemote().sendString("Hello Jetty WebSocket!"); // 等待监听器收到响应,做断言验证(这里可以用CountDownLatch来同步) listener.getLatch().await(5, TimeUnit.SECONDS); assertEquals("Expected response not received", "Received: Hello Jetty WebSocket!", listener.getReceivedMessage()); // 关闭客户端 client.stop(); } // 自定义测试用WebSocket监听器 private static class TestWebSocketListener extends WebSocketAdapter { private final CountDownLatch latch = new CountDownLatch(1); private String receivedMessage; @Override public void onWebSocketText(String message) { receivedMessage = message; latch.countDown(); } public CountDownLatch getLatch() { return latch; } public String getReceivedMessage() { return receivedMessage; } } }
2. 适配Spring WebSocket消息代理(如STOMP)
如果你的项目用了Spring的WebSocket消息代理(比如STOMP协议),不需要手动注册Jetty的WebSocketHandler,而是要在Spring配置中指定Jetty专属的升级策略,让Spring和Jetty的WebSocket机制兼容:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureWebSocketTransport(WebSocketTransportRegistration registration) { // 配置WebSocket传输参数 registration.setMessageSizeLimit(1024 * 1024); registration.setSendTimeLimit(20000); // 指定Jetty的WebSocket升级策略 registration.setUpgradeStrategy(new WebSocketJettyRequestUpgradeStrategy()); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 注册STOMP端点,支持SockJS降级 registry.addEndpoint("/stomp-ws").withSockJS(); } // 其他消息代理配置... }
这种情况下,测试类里只需要注册DispatcherServlet即可,Spring会自动处理WebSocket的升级请求,你可以用STOMP客户端(比如Spring的StompClient)来进行测试。
3. 核心注意事项
- 必须移除
@WebAppConfiguration:这个注解会强制Spring创建MockServletContext,而它不兼容Jetty的Context接口,是问题的核心诱因。 - 手动管理Jetty生命周期:用
@Before和@After注解分别启动、销毁Jetty,确保每个测试用例都有独立干净的容器环境。 - 使用真实客户端测试:不要依赖MockMvc,而是用真实的WebSocket/STOMP客户端来模拟实际场景,这样才能覆盖WebSocket的全流程功能。
通过这种方式,你既可以保留SpringJUnit4ClassRunner来便捷加载Spring上下文,又能绕过MockMvc的兼容性问题,顺利完成Jetty上的WebSocket集成测试。
内容的提问来源于stack exchange,提问作者jmc42

