Currently, whenever I need to fail a test in response to an exception thrown in another thread, I write something like this:
package com.example;
import java.util.ArrayList;
import java.util.List;
import org.testng.annotations.Test;
import static java.util.Arrays.asList;
import static java.util.Collections.synchronizedList;
import static org.testng.Assert.fail;
public final class T {
@Test
public void testFailureFromLambda() throws Throwable {
final List<Throwable> errors = synchronizedList(new ArrayList<>());
asList("0", "1", "2").parallelStream().forEach(s -> {
try {
/*
* The actual code under test here.
*/
throw new Exception("Error " + s);
} catch (final Throwable t) {
errors.add(t);
}
});
if (!errors.isEmpty()) {
errors.forEach(Throwable::printStackTrace);
final Throwable firstError = errors.iterator().next();
fail(firstError.getMessage(), firstError);
}
}
}
A synchronized list may be replaced with an AtomicReference<Throwable>
, but in general the code remains pretty much the same.
Is there any standard (and less verbose) way of doing the same using any of test frameworks available in Java (TestNG, JUnit, Hamcrest, AssertJ, etc.)?
Yes, it can be done by using Thread. UncaughtExceptionHandler. When a thread is about to terminate due to an uncaught exception the Java Virtual Machine will query the thread for its UncaughtExceptionHandler usingThread.
In this case, the tested method should throw an exception, so the test will pass. If you remove the expected = Exception. class from the annotation, the test will fail if an exception occurs.
JUnit provides an option of tracing the exception handling of code. You can test whether the code throws a desired exception or not. The expected parameter is used along with @Test annotation. Let us see @Test(expected) in action.
Normally, best practice avoids "throws Exception". The reason is that it makes exception handling meaningless for the API user. But there is no such user here. So "throws Exception" is the right practice.
By default TestNG fails a test method when an exception is thrown from it. I believe the same thing happens with JUnit as well, wherein it marks a test as errored
, if it throws an unexpected exception.
If you are to be dealing with Streams
, then you would need to wrap it up within a RuntimeException
variant, so that Java doesn't complain. TestNG would automatically fail the test.
Here's a sample :
@Test
public void testFailureFromLambdaRefactored() {
asList("0", "1", "2").parallelStream().forEach(s -> {
try {
/*
* The actual code under test here.
*/
if (s.equals("2")) {
throw new Exception("Error " + s);
}
} catch (final Throwable t) {
throw new RuntimeException(t);
}
});
}
This was for scenarios that involve lambdas and streams. In general if you would like to know about an exception that happens in a new thread spun off from a @Test
method, then you would need to use ExecutorService
.
Here's a sample :
@Test
public void testFailureInAnotherThread() throws InterruptedException, ExecutionException {
List<String> list = asList("0", "1", "2");
ExecutorService service = Executors.newFixedThreadPool(2);
List<Future<Void>> futures = service.invokeAll(Arrays.asList(new Worker(list)));
for (Future future : futures) {
future.get();
}
}
public static class Worker implements Callable<Void> {
private List<String> list;
public Worker(List<String> list) {
this.list = list;
}
@Override
public Void call() throws Exception {
for (String s : list) {
if (s.equals("2")) {
throw new Exception("Error " + s);
}
}
return null;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With