Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Failing a unit test if an exception is thrown in another thread

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.)?

like image 932
Bass Avatar asked Aug 18 '17 15:08

Bass


People also ask

Can you catch exception thrown by another thread?

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.

What happens if a test method throws an exception?

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.

How do you handle exceptions in unit testing?

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.

Should unit test throw exceptions?

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.


1 Answers

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;
    }
}
like image 107
Krishnan Mahadevan Avatar answered Oct 06 '22 08:10

Krishnan Mahadevan