0

What I'm trying to do

I have a server class that has intentionally no arguments passed to it and want to test it with Mockito.

If you want to check out the full source code on Github:

Server.class

public class Server extends Thread {
    private Other other;
    ObjectInputStream fromClient;
    ObjectOutputStream toClient;


    public Server(){
        this.other = new Other(foo, bar);
    }


    @Override
    public void run(){
        try{

            ServerSocket serverSocket = new ServerSocket(1337);
            Socket socket = serverSocket.accept();

            fromClient = new ObjectInputStream(socket.getInputStream());
            toClient = new ObjectOutputStream(socket.getOutputStream());

            while(true) {
                int command = (Integer) fromClient.readObject();
                switch (command) {
                    case 0x1:
                        //add
                        //...
                        break;
                    case 0x2:
                        //get server data
                        toClient.writeObject(other.getSomething());
                        break;
                    case 0x3:
                        //delete
                        //...
                        break;
                    default:
                        break;
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        Thread t = new Server();
        t.start();
    }
}

The Problem

I understand that Mockito can't mock final classes, such as ObjectOutputStream and ObjectInputStream.

This is exactly where I run into Problems.

So far, my test is failing with NullPointerException on line

when(server.fromClient.readObject()).thenReturn(0x2);.

This is typical for Mockito, when running into final methods.

ServerTest.class

@Test
    void run() throws IOException, ClassNotFoundException {
        //given
        Other other = new Other(foo, bar);
        Server server = mock(Server.class);

        //when
        when(server.fromClient.readObject()).thenReturn(0x2);

        server.start();

        //then
        verify(server).toClient.writeObject(other.getSomething());

    }

What I tried

It was suggested in other posts that it's possible to circumvent the final-problem, by changing the signature of the class under test by implementing the interface ObjectInput and therefore mock it anyway.

However in the proposed approach it's unclear how to operate when not passing ObjectOutputStream as an argument to the class under test.

Also, how to operate when you have the ObjectInputStream directly controlling a response of an ObjectOutputStream, as seen with my case structure, which is not untypical for TCP client/server applications.

So far I'm under the impression that my test would work, would it not be for the final keyword in the signature of ObjectOutputStream. Correct me here if I'm wrong.

Q: "Why am I not passing the streams to the server?" A: Because I don't need them anywhere else.

It's really a last ditch effort to do so and I will if I have to, but I would rather not.

blkpingu
  • 1,556
  • 1
  • 18
  • 41

2 Answers2

2

You can provide a function in your Server class that reads fromClient input stream and mock it instead of ObjectInputStream.readObject() method:

public class Server extends Thread {
     private Other other;
     ObjectInputStream fromClient;
     ObjectOutputStream toClient;


     public Server(){
         this.other = new Other(foo, bar);
     }

     Object readObject() throws ClassNotFoundException, IOException {
         return fromClient.readObject();
     }

     //...

So your test would look like this:

    @Test
    void run() throws IOException, ClassNotFoundException {
        //given
        Other other = new Other(foo, bar);
        Server server = mock(Server.class);

        //when
        when(server.readObject()).thenReturn(0x2);

        server.start();

        //then
        verify(server).toClient.writeObject(other.getSomething());
    }

The same thing can be applied to toClient stream as well.

Pavel Smirnov
  • 4,611
  • 3
  • 18
  • 28
  • Tried that earlier with no positive result. – blkpingu Mar 24 '19 at 17:31
  • Still nullpointer, only this time just on the `verify`, instead of on the `when` – blkpingu Mar 24 '19 at 17:40
  • @TobiasKolb, sure there is, because you're mocking the whole `Server` and `toClient` field is also null. Provide a mock for it too, or, you may reconsider to use a Spy instead. – Pavel Smirnov Mar 24 '19 at 17:41
  • @TobiasKolb, also, in your Server's `run()` method you should use `readObject()` function as well, instead of applying to `fromClient` directly. – Pavel Smirnov Mar 24 '19 at 17:43
  • I did. I added your changes and pushed it to github. Find the links at the beginning of the question. – blkpingu Mar 24 '19 at 17:50
  • @TobiasKolb, try this: `verify(server).writeObject(null, warehouse.getAllCargo());` – Pavel Smirnov Mar 24 '19 at 18:06
  • @TobiasKolb, Expected behavior. As I said before, you're mocking the whole `Server` object, so the `run()` method is empty. You probably want to use Spying instead of Mocking. If you put `server.writeObject(null, warehouse.getAllCargo())` before `verify()` everything should work correctly, but that IS NOT what you want to do. Check out this: https://stackoverflow.com/questions/28295625/mockito-spy-vs-mock and replace mocking with spying. – Pavel Smirnov Mar 24 '19 at 18:12
  • @TobiasKolb, your `toClient` and `fromClient` objects are still null, but you operate them. Don't do that. Do not operate on streams directly. Use your `readObject()` and `writeObject()` methods, mock them as needed, and operate them only. You also do not need those arguments in the methods. Remove them. The streams are just the details of Server implementation. No class should operate them or even know about their existence. – Pavel Smirnov Mar 24 '19 at 18:46
  • Still nullpointer. Makes no difference. :( https://pastebin.com/ngpyNngs Can you drop me a hint? I'm lost here. – blkpingu Mar 24 '19 at 18:59
  • @TobiasKolb Have you replaced you direct access to streams in Server's `run()` method with `readObject()` and `writeObject()` methods? – Pavel Smirnov Mar 24 '19 at 19:05
  • yes. As seen [here](https://github.com/BlkPingu/ClientServerFXML/blob/master/src/main/java/backend/fxmlBackend/Server/Server.java) – blkpingu Mar 24 '19 at 19:07
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/190590/discussion-between-pavel-smirnov-and-tobias-kolb). – Pavel Smirnov Mar 24 '19 at 19:12
1

Misunderstanding in your question

I understand that Mockito can't mock final classes, such as ObjectOutputStream and ObjectInputStream. > This is exactly where I run into Problems.

So far, my test is failing with NullPointerException on line

when(server.fromClient.readObject()).thenReturn(0x2);.

This is typical for Mockito, when running into final methods.

Your NPE is not caused from Mockitos inability to mock final methods. The reason for the NPE is that your member variables fromClient and toClient are not initialized when your test code tries to access them.

But even if, they would be initialized with instances of the real dependencies and not with mocks so that Mockito wouldn't be able to configure them anyway.

You fundamentally misunderstand the purpose and the technical implications of mocks. You never mock your class under test. You always mock the dependencies of your class under test and replace the real dependencies of your Code Under Test with this mocks.

It was suggested in other posts that it's possible to circumvent the final-problem, by changing the signature of the class under test by implementing the interface ObjectInput and therefore mock it anyway.

No.

You where suggested to to program against interfaces instead of programming against concrete classes and to use Dependency Injection / Inversion of control.

No one suggested tat you class under test (which is your Server class) should implement any interface.

Violating OOP best practices

Q: "Why am I not passing the streams to the server?"
A: Because I don't need them anywhere else.

It's really a last ditch effort to do so and I will if I have to, but I would rather not.

One of the main reasons why we do OOP is re-usability of our code.

A unit test is the most obvious first re-usage of your code. Having trouble testing your code reveals the lack of re-usability. At this point it does not matter that your Server class is not meant to be "re-used" at all. It is your general coding approach that will let you run into trouble in future.

Your Server class has some violations of OOP best practices:

  • encapsulation/information hidig.

    You declared the member variables fromClient and toClient package private most likely in order to be able to access them from our test. This is problematic in two ways:

    1. Your code should never expose implementation details like member variables (except it is a DTO/ValueObject).
    2. You should never modify your production code explicitly for enabling testing.
  • separation of concerns

    Any class (providing business logic) should have a single responsibility. Instantiation of dependencies is a completely different responsibility apart from what ever your business logic is. So your Server class should not be responsible for doing this instantiation.

    I know that this is a bit dogmatic, but it leads to better testable and hence better re-usable code. And again: it does not matter if your code is currently not meant to be re-used at all.

Conclusion

IMHO your real question is: How can I test my code without adapting best practices of OOP?

In that case you should explicitly commit your surrender to bad design by using PowerMock.

Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51