lundi 30 novembre 2015

Mock a "blocking" method call with Spock?

Background

I'm learning to use Spock for unit testing and I've come across an issue I can't seem to get my head around:

Note: This example is very simplified, but it gets the idea of what I'd like to achieve across.

I have a class (call it Listener) which accepts a java.net.ServerSocket as a constructor parameter; it has a startListening method, which spawns a new thread which does the following (heavily reduced for brevity):

while(listening) {
    try {
        Socket socket = serverSocket.accept();
        doSomethingWithSocket(socket);
    } catch(IOException ex) {
        ex.printStackTrace();
        listening = false;
    }
}

In normal operation, the serverSocket.accept() call blocks until a connection is made to the ServerSocket.

Problem

I'd like to be able to test the interactions on the Socket returned by serverSocket.accept(). I can do this with Spock in the following way:

given: "A ServerSocket, Socket, and Listener"
    def serverSocket = Mock(ServerSocket)
    def socket = Mock(Socket)
    serverSocket.accept() >> socket
    def listener = new Listener(serverSocket)

when: "Listener begins listening"
    listener.startListening()

then: "Something should be done with the socket"
    // Verify some behavior on socket

At first glance, this works fine, except that every call to serverSocket.accept() will return the mocked Socket. Since this call is (intentionally) being invoked an indefinite number of times (because I want to accept an indefinite number of inbound connections) all of the interactions on the mock Socket occur an indefinite number of times (depending on how fast the machine is, how long it takes to run, etc...)

Using cardinality

I could use the cardinality of the interaction to specify at least one interaction, like so:

1.._ * socket.someMethod()

But something about that rubs me the wrong way; I'm not really looking for at least one interaction, I'm really looking for one interaction.

Returning null

I could do something like this (to return the Mocked socket once and then null):

serverSocket.accept() >>> [socket, null]

But then I still have tons of calls to doSomethingWithSocket that pass a null parameter, which I then have to check for and ignore (or report). If I ignore it, I might miss reporting a legitimate issue (I don't think ServerSocket#accept can ever return null, but since the class isn't final maybe someone implements their own version which can) but if I report it, my test logs get polluted with the log message that's reporting an expected outcome as an unexpected one.

Using closures and side-effects

I'm admittedly not a Groovy programmer by trade and this is the first time I've worked with Spock, so my apologies if this is a just plain wrong thing to do or if I'm misunderstanding something about how Spock does mocking

I tried this:

serverSocket.accept() >> socket >> {while(true) {}; null }

This loops forever before the accept method is even called; I'm not 100% sure why, as I didn't think the closure would be evaluated until the accept method was called a second time?

I also tried this:

serverSocket.accept() >>> [socket, { while(true){}; null }]

It's my understanding that when the accept method is called the first time, socket will be returned. Further calls will invoke the closure, which loops infinitely and should therefore block.

Locally, this appears to work, but when the build is run by a CI service (specifically, Travis CI) I still see test output indicating that the accept method is retuning null, which is a little bit confusing.

Am I just trying to do something that can't be done? This isn't a deal breaker for me or anything (I can live with noisy test logs) but I'd really like to know if this is possible.

Aucun commentaire:

Enregistrer un commentaire