Huckle
Huckle

Reputation: 1940

Creating a thread with a Mockito mocked Runnable

I have a class that implements Runnable called DoThingInALoop. It is given to a second class that will create a thread out of it, call this thread Boss. The Boss can tell the runnable to quit and let the thread terminate. Later, it can create a new thread with that same DoThingInALoop.

When I am writing unit tests for the Boss I want to mock the DoThingInALoop because it is not the object under test. However, DoThingInALoop extends a class Loop and the code for Loop is actually being executed in the thread spawned by the Boss. That code of course runs into NullPointerExceptions because none of the member variables are set, because the object is mocked.

How can I prevent Java's Thread from "seeing through" the mock?

public class Loop implements Runnable {
    private final Object member = new Object();
    public final void run() {
        // This code runs, but it shouldn't
        synchronized (member) { // Throws NPE
            ...
        }
        // Hook for subclasses
        try {
            subclassRun();
        } finally {
            // Mandatory cleanup (unrelated to question)
        }
}

public class DoThingInALoop extends Loop {
    public void subclassRun() { ... }
    public void stopDoingThingsInALoop() {
        // Set instance member that on next loop iteration signals thread to return
    }
}

public class Boss {
    private final DoThingsInALoop toBeMocked;
    public Boss(final DoThingsInALoop toBeMocked) {
        this.toBeMocked = toBeMocked;
    }
    public void start() {
        // Simplified
        new Thread(toBeMocked).start();
    }
    public void stop() {
        toBeMocked.stopDoingThingsInALoop();
    }
}


public class TestClass {
    @Test
    public void aTest() {
        // Setup Mocks
        DoThingsInALoop mockLoop = mock(DoThingsInALoop.class);
        Boss boss = new Boss(mockLoop);

        // Run test
        boss.start();

        // After the above line runs, a new thread is started and
        // the run() method of `Loop` executes until the NPE
        // is hit when it attempts to access a member variable
        // which is of course not set because it is a mocked object

        ...
    }
}

Upvotes: 3

Views: 9077

Answers (1)

Ruben
Ruben

Reputation: 4056

I think this is too much final for Mockito.

I would encapsulate the Thread execution in a new class that you can easily mock (let's call it Executor).

public class Executor {
    public void execute(Runnable runnable) {
        new Thread(runnable).start();
    }
}

And then use the Executor in the place when you create the Thread.

public class Boss {
    private final DoThingInALoop toBeMocked;
    private final Executor executor;

    public Boss(final Executor executor, final DoThingInALoop toBeMocked) {
        this.executor = executor;
        this.toBeMocked = toBeMocked;
    }

    public void start() {
        executor.execute(toBeMocked);
    }
}

In your test, you just need to mock the Executor.

    DoThingInALoop mockLoop = Mockito.mock(DoThingInALoop.class);
    Executor mockExecutor = Mockito.mock(Executor.class);
    Boss boss = new Boss(mockExecutor, mockLoop);

    boss.start();

    // No NPE aynymore
    verify(mockExecutor).execute(mockLoop);

Another option would be to try PowerMock

Upvotes: 2

Related Questions