Unit Test For Websocket Returns Null
Asked Answered
O

2

1

I am trying to implement unit test for a Websocket in Springboot with JUnit.

I am following this link and it runs fine but gets null in the result, when i test my websocket with a browser it sends the desire results.

let me show my test class

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes =Main.class)
@WebAppConfiguration
public class WebSocketTest {

static final String WEBSOCKET_URI = "http://localhost:8080/api/realtimedata";
static final String WEBSOCKET_TOPIC = "/realtimedata";

BlockingQueue<WebSocketData> blockingQueue;
WebSocketStompClient stompClient;

@BeforeEach
public void setup() {

    blockingQueue = new LinkedBlockingDeque<>();
    stompClient = new WebSocketStompClient(new SockJsClient(
            asList(new WebSocketTransport(new StandardWebSocketClient()))));
}

@Test
public void shouldReceiveAMessageFromTheServer() throws Exception {
    StompSession session = stompClient
            .connect(WEBSOCKET_URI, new StompSessionHandlerAdapter() {})
            .get(1, SECONDS);
    session.subscribe(WEBSOCKET_TOPIC, new DefaultStompFrameHandler());

    String message = "{\"groupId\":\"grp-1\", \"accountId\":\"acc-1\", \"userId\":\"usr-1\"}";
    session.send(WEBSOCKET_TOPIC, message.getBytes()); 
    Assertions.assertEquals("trying to figureout", blockingQueue.poll(1, SECONDS));
}

class DefaultStompFrameHandler implements StompFrameHandler {
    @Override
    public Type getPayloadType(StompHeaders stompHeaders) {
        return WebSocketData.class;
    }

    @Override
    public void handleFrame(StompHeaders stompHeaders, Object o) {
        blockingQueue.offer((WebSocketData) o);
    }
}

}

Here is my WebSocketConfiguration class

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableSimpleBroker("/realtimedata");
    registry.setApplicationDestinationPrefixes("/wsconn");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/realtimedata")
            .setAllowedOrigins("*")
            .withSockJS();
}

}

This is response I get from server

org.opentest4j.AssertionFailedError: 
Expected :trying to figureout
Actual   :null

Update:

After adding the changes recommended by Rieckpil in answer below and with some further research my test class looks like this now

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ServerWebSocketTest {

@LocalServerPort
private Integer port;

static final String WEBSOCKET_TOPIC = "/realtimedata";

BlockingQueue<WebSocketData> blockingQueue;
WebSocketStompClient stompClient;

@BeforeEach
public void setup() {

    blockingQueue = new LinkedBlockingDeque<>();
    stompClient = new WebSocketStompClient(new SockJsClient(
            asList(new WebSocketTransport(new StandardWebSocketClient()))));

    // might be required
    stompClient.setMessageConverter(new MappingJackson2MessageConverter());

}

@Test
public void shouldReceiveAMessageFromTheServer() throws Exception {
    StompSession session = stompClient
            .connect(getWsPath(), **new DefaultStompFrameHandler()** {
            })
            .get(1, TimeUnit.SECONDS);
    session.subscribe(WEBSOCKET_TOPIC, new DefaultStompFrameHandler());

    String message = "{\"groupId\":\"grp-1\", \"accountId\":\"acc-1\", \"userId\":\"usr-1\"}";
    session.send(WEBSOCKET_TOPIC, message.getBytes());
    Assertions.assertEquals("trying to figureout", blockingQueue.poll(1, TimeUnit.SECONDS));
}

class DefaultStompFrameHandler **extends StompSessionHandlerAdapter**{   
    @Override
    public Type getPayloadType(StompHeaders stompHeaders) {
        return WebSocketData.class;
    }

    @Override
    public void handleFrame(StompHeaders stompHeaders, Object o) {
        blockingQueue.offer((WebSocketData) o);
    }

    **@Override
    public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
        exception.printStackTrace();
    }**
}
private String getWsPath() {
    return String.format("ws://localhost:%d/location_services/realtimedata", port);
}

}

After these changes I am now able to get the stacktraces to print in logs

here is the exception that I got after these changes

 org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Cannot construct instance of `com.convo.locationservices.websocket.WebSocketData` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('eyJncm91cElkIjoiZ3JwLTEiLCAiYWNjb3VudElkIjoiYWNjLTEiLCAidXNlcklkIjoidXNyLTEifQ==')
 at [Source: (byte[])""eyJncm91cElkIjoiZ3JwLTEiLCAiYWNjb3VudElkIjoiYWNjLTEiLCAidXNlcklkIjoidXNyLTEifQ==""; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.convo.locationservices.websocket.WebSocketData` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('eyJncm91cElkIjoiZ3JwLTEiLCAiYWNjb3VudElkIjoiYWNjLTEiLCAidXNlcklkIjoidXNyLTEifQ==')
 at [Source: (byte[])""eyJncm91cElkIjoiZ3JwLTEiLCAiYWNjb3VudElkIjoiYWNjLTEiLCAidXNlcklkIjoidXNyLTEifQ==""; line: 1, column: 1], failedMessage=GenericMessage [payload=byte[82], headers={simpMessageType=MESSAGE, stompCommand=MESSAGE, nativeHeaders={destination=[/realtimedata], content-type=[application/json], subscription=[0], message-id=[cc55448f0b4f447d8f6bc7662acd15db-0], content-length=[82]}, simpSubscriptionId=0, contentType=application/json, simpSessionId=2df73ca0-e13b-c479-4591-37e9f7e8d689, simpDestination=/realtimedata}]
    at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:235)
    at org.springframework.messaging.converter.AbstractMessageConverter.fromMessage(AbstractMessageConverter.java:197)
    at org.springframework.messaging.converter.AbstractMessageConverter.fromMessage(AbstractMessageConverter.java:188)
    at org.springframework.messaging.simp.stomp.DefaultStompSession.invokeHandler(DefaultStompSession.java:477)
    at org.springframework.messaging.simp.stomp.DefaultStompSession.handleMessage(DefaultStompSession.java:429)
    at org.springframework.web.socket.messaging.WebSocketStompClient$WebSocketTcpConnectionHandlerAdapter.handleMessage(WebSocketStompClient.java:340)
    at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
    at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56)
    at org.springframework.web.socket.sockjs.client.AbstractClientSockJsSession.handleMessageFrame(AbstractClientSockJsSession.java:294)
    at org.springframework.web.socket.sockjs.client.AbstractClientSockJsSession.handleFrame(AbstractClientSockJsSession.java:230)
    at org.springframework.web.socket.sockjs.client.WebSocketTransport$ClientSockJsWebSocketHandler.handleTextMessage(WebSocketTransport.java:163)
    at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:114)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:43)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:85)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:82)
    at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:395)
    at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:495)
    at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294)
    at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133)
    at org.apache.tomcat.websocket.WsFrameClient.processSocketRead(WsFrameClient.java:95)
    at org.apache.tomcat.websocket.WsFrameClient.resumeProcessing(WsFrameClient.java:209)
    at org.apache.tomcat.websocket.WsFrameClient.access$300(WsFrameClient.java:31)
    at org.apache.tomcat.websocket.WsFrameClient$WsFrameClientCompletionHandler.doResumeProcessing(WsFrameClient.java:186)
    at org.apache.tomcat.websocket.WsFrameClient$WsFrameClientCompletionHandler.completed(WsFrameClient.java:163)
    at org.apache.tomcat.websocket.WsFrameClient$WsFrameClientCompletionHandler.completed(WsFrameClient.java:148)
    at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:127)
    at java.base/sun.nio.ch.Invoker$2.run(Invoker.java:219)
    at java.base/sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.convo.locationservices.websocket.WebSocketData` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('eyJncm91cElkIjoiZ3JwLTEiLCAiYWNjb3VudElkIjoiYWNjLTEiLCAidXNlcklkIjoidXNyLTEifQ==')
 at [Source: (byte[])""eyJncm91cElkIjoiZ3JwLTEiLCAiYWNjb3VudElkIjoiYWNjLTEiLCAidXNlcklkIjoidXNyLTEifQ==""; line: 1, column: 1]
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1432)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1062)
    at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:323)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1373)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:171)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3309)
    at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:219)
    ... 31 more
            

org.opentest4j.AssertionFailedError: 
Expected :trying to figureout
Actual   :null
 

Can anyone please guid me what am I doing wrong?

Thanks.

Oxidation answered 13/7, 2020 at 6:24 Comment(3)
can you add your WebSocket configuration? Also, are you sure you actually connect to your loaded context and not to your application which is running in parallel on port 8080?Dey
@Dey thanks for replying, i have added the websocket configuration class. And if i stop my application which is running websocket on port 8080, this unit test gives me exception..... "org.springframework.web.client.ResourceAccessException: I/O error on GET request for "localhost:8080/api/realtimedata/info": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)".... "Caused by: java.net.ConnectException: Connection refused (Connection refused)"Oxidation
I've updated my answer, try it with ByteArrayMessageConverter. If that does not work, please also include the server-side where you accept the incoming payloadDey
D
1

You should configure your integration test to use the started Spring context and don't hardcode 8080.

Furthermore, ensure you have a MessageConverter configured for your WebSocketStompClient that fits your use case. The default is SimpleMessageConverter. As you are sending byte[], the ByteArrayMessageConverter should fit.

Your test can be refactored to the following:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Test {

  @LocalServerPort
  private Integer port;
  
  static final String WEBSOCKET_TOPIC = "/realtimedata";

  BlockingQueue<WebSocketData> blockingQueue;
  WebSocketStompClient stompClient;

  @BeforeEach
  public void setup() {

    blockingQueue = new LinkedBlockingDeque<>();
    stompClient = new WebSocketStompClient(new SockJsClient(
      asList(new WebSocketTransport(new StandardWebSocketClient()))));

    webSocketStompClient.setMessageConverter(new ByteArrayMessageConverter()); 

  }

  @Test
  public void shouldReceiveAMessageFromTheServer() throws Exception {
    StompSession session = stompClient
      .connect(getWsPath(), new StompSessionHandlerAdapter() {
      })
      .get(1, SECONDS);
    session.subscribe(WEBSOCKET_TOPIC, new DefaultStompFrameHandler());

    String message = "{\"groupId\":\"grp-1\", \"accountId\":\"acc-1\", \"userId\":\"usr-1\"}";
    session.send(WEBSOCKET_TOPIC, message.getBytes());
    Assertions.assertEquals("trying to figureout", blockingQueue.poll(1, SECONDS));
  }

  class DefaultStompFrameHandler implements StompFrameHandler {
    @Override
    public Type getPayloadType(StompHeaders stompHeaders) {
      return WebSocketData.class;
    }

    @Override
    public void handleFrame(StompHeaders stompHeaders, Object o) {
      blockingQueue.offer((WebSocketData) o);
    }
  }
  
  private String getWsPath() {
    return String.format("ws://localhost:%d/api/realtimedata", port);
  }

}

This test will start the whole Spring context, the embedded servlet container on a random port, and try to connect to your endpoint.

Dey answered 14/7, 2020 at 5:33 Comment(6)
Thanks for putting in your time , really appreciate it. However I am confused about the MessageConverter that you have mentioned, should it convert the json(response) into the model class that is being returned by the websocket to the client app?Oxidation
as I don't know how your production code looks like (how you work with the payload) I added this note in advance. First, try it without any custom configured message converter.Dey
the response is same either with or without MappingJackson2MessageConverter(). I further researched this issue and found that by default the exception is not printed , in order to do so, I should override handleException() in DefaultStompFrameHandler and it should extend StompSessionHandlerAdapter instead of StompFrameHandler After making the changes exception is now showing in the logsOxidation
can you add the exception strack trace that now appears to your question?Dey
I have added the new changes and the exception stacktraces in my question.Oxidation
actually i can’t change anything on server side so I am now sending simple string as message, it still gives me,,,,,,,, 'org.springframework.messaging.converter.MessageConversionException: Could not read JSON' I am marking your answer as 'accepted' and posting another question related to this exception if you have any solution please do let me know i will copy link here in a while Thanks for your time and efforts, much appreciatedOxidation
V
-1

I've encounter the same problem. In my case I forgot to add default constructor and all arguments constructor

please check your WebSocketData class to add constructors

Valdez answered 7/12, 2021 at 21:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.