How to test a Java EE7 Websocket
Asked Answered
R

1

9

I have implemented a WebSocket using the api provided with Java EE 7. Additionally I have implemented a client that requests my WebSocket without any problems. To be sure this remains working when performing some code changes I want to implement tests which can also be run on a build server like e.g. Jenkins CI and not only locally. I am using maven.

Here's my server endpoint:

import javax.enterprise.context.ApplicationScoped;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

@ApplicationScoped
@ServerEndpoint("/example")
public class WebSocket {

    private final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());

    @OnOpen
    public void open(Session session) {
        sessions.add(session);
    }

    @OnClose
    public void close(Session session) {
        sessions.remove(session);
    }

    @OnError
    public void onError(Throwable error) {
        //TODO do something
    }

    @OnMessage
    public void handleMessage(String message, Session session) throws IOException {
        session.getBasicRemote().sendText("Hello "+message+"!");
    }
}

Here's my client endpoint:

import javax.websocket.*;
import java.io.IOException;
import java.net.URI;

@ClientEndpoint
public class WebsocketClient {

    private String uri = "ws://localhost:8181/<some-path>/example";
    private Session userSession = null;
    private String answer;

    public WebsocketClient() {
        try {
            WebSocketContainer container = ContainerProvider
                .getWebSocketContainer();
            container.connectToServer(this, new URI(uri));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public boolean isClosed() {
        return userSession == null || !userSession.isOpen();
    }

    @OnOpen
    public void onOpen(Session userSession) {
        this.userSession = userSession;
    }

    @OnClose
    public void onClose(Session userSession, CloseReason reason) {
        this.userSession = null;
    }

    @OnMessage
    public void onMessage(String message) {
        this.answer = message;
    }

    public String getAnswer() {
        return answer;
    }

    public void sendMessage(String message) {
        if (userSession != null && userSession.isOpen())
            try {
                this.userSession.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        else {
            System.out.println("Session closed!");
        }
    }

    public void reset() {
        this.answer = null;
    }
}

I also have some other classes that are tested on the build server that use CDI. To be able to use the CDI and other application container functionality I am using the EJBContainerRunner with apache TomeEE/OpenEJB in version 7.0.0-M3 which starts an embedded TomEE runs my tests and shuts it down afterwards. Using this approach I don't have an URL to call. This works fine with e.g. JAX-RS REST services where you can simply call your classes methods in your tests and check the responses. But how do you do something like this with WebSockets? I can't simply call the methods due to the missing Session.

My current test looks like this:

import org.apache.openejb.junit.jee.EJBContainerRunner;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.net.SocketException;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

@RunWith(EJBContainerRunner.class)
public class TestWebsocket {

    private WebsocketClient socket;

    @Test
    public void test() throws SocketException {
        String answer = requestSynchronous("Java");
        assertThat(answer, is(equalTo("Hello Java!")));
    }

    public String requestSynchronous(String message) throws SocketException {
        if (socket == null || socket.isClosed()) {
             socket = new WebsocketClient();
        }
        socket.reset();
        socket.sendMessage(message);
        String answer = null;
        int i = 0;
        while (answer == null && i < 10000) {
            try {
                answer = socket.getAnswer();
                Thread.sleep(1);
                i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (answer != null)
            return answer;
        else throw new SocketException("Connection timeout");
    }

}

This works running a TomEE on my local machine with e.g. the tomee-maven-plugin. But this approach can't be done on the build server. Is there a simpler way than configuring special ports or setting up a virtual machine for testing?

Thanks in advance!

Robby answered 23/4, 2016 at 17:45 Comment(0)
R
7

I found a solution for my testing problem without mocking, using a vm, ...

For sending an answer message to the client it is not needed to call session.getBasicRemote().sendText("...");. Instead return the message you want to send.

The handleMessage method in the class WebSocket changes to:

@OnMessage
public String handleMessage(String message){
    return "Hello "+message+"!";
}

And the test simplifies to:

@RunWith(EJBContainerRunner.class)
public class TestWebsocket {

    @Inject
    private WebSocket socket;

    @Test
    public void test() throws SocketException {
        String answer = socket.handleMessage("Java");
        assertThat(answer, is(equalTo("Hello Java!")));
    }   
}

This approach doesn't send messages to the outside of the web application container that is started for the test. This makes it much easier to handle the test.

Robby answered 24/4, 2016 at 11:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.