Reputation: 1940
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 NullPointerException
s 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
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