How do I test this method's expected behavior in Java: it spawn a thread, and throws an exception under certain conditions
Suppose that I have a method which spawns a new thread and do some work. Under certain conditions, the newly spawn thread would throw a certain type of exception, which terminates the entire process. I would like to write JUnit tests to verify this behavior. Is there a way to do it?
The method is:
private void foo() {
new Thread() {
@Override void run() {开发者_如何学Go
throw new CertainException("exception messages");
}
}.start();
}
In test (conceptually):
public testExceptionThrownFromNewThread() throws Exception {
try {
foo();
Thread.sleep(5000); // wait for the exception to be thrown
fail();
} catch (CertainException e) {
assertEquals(e.message, "exception messages");
}
}
This test doesn't work because the exception spawn from the other thread cannot be caught.
If you want to test just the code inside of the run() method, refactor it ouf of the foo() method (probably into a Runnable) and test it separately without running it from a thread.
private void foo() {
new Thread(new MyRunnable()).start();
}
public class MyRunnable implements Runnable {
public void run() {
....
}
}
Now you can instantiate a MyRunnable object and call the run() method from your test without needing to start a thread.
EDIT
Testing of the thread creation could be done by using a ThreadFactory Mock. (as Jon Skeet pointed out).
You could overwrite the default UncaughtExceptionHandler
for Threads. It gets called whenever a Thread throws an exception. In this handler, you can check whether the expected exception is equal to the thrown exception and e.g. test for messages or count the occurences of the exception. By using a CountDownLatch
, you can also check whether the exceptions are thrown in time and how many of them you expect.
This works even if you do not have access to the Thread
created by the class under test. If you have access to it though, there is certainly an easier approach, e.g. refactoring the class under test and introduce an Exception Listener or alike. Make the class under test better testable also improves the design, e.g. by removing the dependency on Threads and directly test the body of the run() method which you could externalize.
public class ThreadExceptionTest {
private void foo() {
new Thread(new Runnable() {
@Override
public void run() {
throw new RuntimeException("exception messages");
}
}).start();
}
@Test
public void testFoo() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final RuntimeException expectedException = new RuntimeException("exception messages");
UncaughtExceptionHandler eh = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
if (e.getMessage().equals(expectedException.getMessage()))
latch.countDown();
}
};
Thread.setDefaultUncaughtExceptionHandler(eh);
foo();
assertTrue(latch.await(100,TimeUnit.MILLISECONDS));
}
}
Well, unit tests are supposed to verify results of method calls, not implementation details.
In your library, if thread terminates, how does it affect library user? Maybe computations won't be finished and end results won't be recored in database? Then check database. Maybe thread will stop doing some periodic tasks (like cleanup)? Then check whether cleanup is still being done.
And if exception thrown won't affect user in any way, then there's nothing to check. Because whether exception is thrown or not is just an implementation details (user will never see it).
One option is to make the capability to start a thread a dependency - which you can specify using the existing ThreadFactory
interface. Then in your unit test you can provide a specialist ThreadFactory
which wraps the given Runnable
in order to record exceptions etc.
You'll be able to test that:
- The
ThreadFactory
was used - The thread was started
- The operation threw an exception
精彩评论