Add WebSocket integration tests w/ Java configuration

Issue: SPR-10835
This commit is contained in:
Rossen Stoyanchev
2013-08-28 21:08:17 -04:00
parent 744e1ed203
commit f0dda0e38b
20 changed files with 686 additions and 295 deletions

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.simp;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.runners.Parameterized.Parameter;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.server.DefaultHandshakeHandler;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.RequestUpgradeStrategy;
import org.springframework.web.socket.server.support.JettyRequestUpgradeStrategy;
/**
* Base class for WebSocket integration tests.
*
* @author Rossen Stoyanchev
*/
public abstract class AbstractWebSocketIntegrationTests {
private static Map<Class<?>, Class<?>> upgradeStrategyConfigTypes = new HashMap<Class<?>, Class<?>>();
static {
upgradeStrategyConfigTypes.put(JettyTestServer.class, JettyUpgradeStrategyConfig.class);
}
@Parameter(0)
public TestServer server;
@Parameter(1)
public WebSocketClient webSocketClient;
@Before
public void setup() throws Exception {
if (this.webSocketClient instanceof Lifecycle) {
((Lifecycle) this.webSocketClient).start();
}
}
@After
public void teardown() throws Exception {
try {
if (this.webSocketClient instanceof Lifecycle) {
((Lifecycle) this.webSocketClient).stop();
}
}
finally {
this.server.stop();
}
}
protected String getWsBaseUrl() {
return "ws://localhost:" + this.server.getPort();
}
protected Class<?> getUpgradeStrategyConfigClass() {
return upgradeStrategyConfigTypes.get(this.server.getClass());
}
static abstract class AbstractRequestUpgradeStrategyConfig {
@Bean
public HandshakeHandler handshakeHandler() {
return new DefaultHandshakeHandler(requestUpgradeStrategy());
}
public abstract RequestUpgradeStrategy requestUpgradeStrategy();
}
@Configuration
static class JettyUpgradeStrategyConfig extends AbstractRequestUpgradeStrategyConfig {
@Bean
public RequestUpgradeStrategy requestUpgradeStrategy() {
return new JettyRequestUpgradeStrategy();
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.simp;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.util.SocketUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.socket.TestServer;
/**
* Jetty based {@link TestServer}.
*
* @author Rossen Stoyanchev
*/
public class JettyTestServer implements TestServer {
private final Server jettyServer;
private final int port;
public JettyTestServer() {
this.port = SocketUtils.findAvailableTcpPort();
this.jettyServer = new Server(this.port);
}
@Override
public int getPort() {
return this.port;
}
@Override
public void init(WebApplicationContext cxt) {
ServletContextHandler handler = new ServletContextHandler();
handler.addServlet(new ServletHolder(new DispatcherServlet(cxt)), "/");
this.jettyServer.setHandler(handler);
}
@Override
public void start() throws Exception {
this.jettyServer.start();
}
@Override
public void stop() throws Exception {
if (this.jettyServer.isRunning()) {
this.jettyServer.stop();
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.simp;
import org.springframework.web.context.WebApplicationContext;
/**
* Contract for a test server to use for integration tests.
*
* @author Rossen Stoyanchev
*/
public interface TestServer {
int getPort();
void init(WebApplicationContext cxt);
void start() throws Exception;
void stop() throws Exception;
}

View File

@@ -16,28 +16,37 @@
package org.springframework.messaging.simp.config;
import org.junit.Before;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.websocket.SubProtocolWebSocketHandler;
import org.springframework.messaging.simp.AbstractWebSocketIntegrationTests;
import org.springframework.messaging.simp.JettyTestServer;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.simp.stomp.StompMessageConverter;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.channel.ExecutorSubscribableChannel;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter;
import org.springframework.web.socket.client.jetty.JettyWebSocketClient;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.config.WebSocketConfigurationSupport;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler;
import org.springframework.web.socket.support.TestWebSocketSession;
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;
import static org.junit.Assert.*;
@@ -47,65 +56,47 @@ import static org.junit.Assert.*;
*
* @author Rossen Stoyanchev
*/
public class WebSocketMessageBrokerConfigurationTests {
@RunWith(Parameterized.class)
public class WebSocketMessageBrokerConfigurationTests extends AbstractWebSocketIntegrationTests {
@Parameters
public static Iterable<Object[]> arguments() {
return Arrays.asList(new Object[][] {
{ new JettyTestServer(), new JettyWebSocketClient()} });
};
@Before
public void setup() {
}
@Test
public void webSocketHandler() throws Exception {
public void sendMessage() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext();
AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
cxt.register(TestWebSocketMessageBrokerConfiguration.class, SimpleBrokerConfigurer.class);
cxt.refresh();
cxt.register(getUpgradeStrategyConfigClass());
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) cxt.getBean(HandlerMapping.class);
Object actual = hm.getUrlMap().get("/e1");
assertNotNull(actual);
assertEquals(WebSocketHttpRequestHandler.class, actual.getClass());
cxt.close();
}
@Test
public void webSocketHandlerWithSockJS() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext();
cxt.register(TestWebSocketMessageBrokerConfiguration.class, SimpleBrokerConfigurer.class);
cxt.refresh();
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) cxt.getBean(HandlerMapping.class);
Object actual = hm.getUrlMap().get("/e2/**");
assertNotNull(actual);
assertEquals(SockJsHttpRequestHandler.class, actual.getClass());
cxt.close();
}
@Test
public void annotationMethodMessageHandler() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext();
cxt.register(TestWebSocketMessageBrokerConfiguration.class, SimpleBrokerConfigurer.class);
cxt.refresh();
this.server.init(cxt);
this.server.start();
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SEND);
headers.setDestination("/app/foo");
Message<byte[]> message = MessageBuilder.withPayloadAndHeaders(new byte[0], headers).build();
byte[] bytes = new StompMessageConverter().fromMessage(message);
final TextMessage webSocketMessage = new TextMessage(new String(bytes));
TestWebSocketSession session = new TestWebSocketSession();
session.setAcceptedProtocol("v12.stomp");
WebSocketHandler clientHandler = new TextWebSocketHandlerAdapter() {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
session.sendMessage(webSocketMessage);
}
};
SubProtocolWebSocketHandler wsHandler = cxt.getBean(SubProtocolWebSocketHandler.class);
wsHandler.handleMessage(session, new TextMessage(new String(bytes)));
TestController testController = cxt.getBean(TestController.class);
assertTrue(cxt.getBean(TestController.class).foo);
this.webSocketClient.doHandshake(clientHandler, getWsBaseUrl() + "/ws");
assertTrue(testController.latch.await(2, TimeUnit.SECONDS));
cxt.close();
testController.latch = new CountDownLatch(1);
this.webSocketClient.doHandshake(clientHandler, getWsBaseUrl() + "/sockjs/websocket");
assertTrue(testController.latch.await(2, TimeUnit.SECONDS));
}
@@ -128,16 +119,23 @@ public class WebSocketMessageBrokerConfigurationTests {
public TestController testController() {
return new TestController();
}
}
@Configuration
static class SimpleBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
@Autowired
private HandshakeHandler handshakeHandler; // can't rely on classpath for server detection
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/e1");
registry.addEndpoint("/e2").withSockJS();
registry.addEndpoint("/ws")
.setHandshakeHandler(this.handshakeHandler);
registry.addEndpoint("/sockjs").withSockJS()
.setTransportHandlerOverrides(new WebSocketTransportHandler(this.handshakeHandler));;
}
@Override
@@ -150,12 +148,11 @@ public class WebSocketMessageBrokerConfigurationTests {
@Controller
private static class TestController {
private boolean foo;
private CountDownLatch latch = new CountDownLatch(1);
@MessageMapping(value="/app/foo")
public void handleFoo() {
this.foo = true;
this.latch.countDown();
}
}

View File

@@ -19,6 +19,10 @@
<level value="info" />
</logger>
<logger name="org.springframework.web">
<level value="debug" />
</logger>
<!-- Root Logger -->
<root>
<priority value="warn" />