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

1

7

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

Kellam answered 18/8, 2017 at 15:11 Comment(0)
C
4

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;
    }
}
Castrate answered 19/8, 2017 at 3:25 Comment(3)
What if I want to prevent failing the whole test and continue soft assertions if an exception is thrown in one of the soft assertions?Gadabout
@Gadabout - By default SoftAssertions fail a test only after all the assertions have been validated.Castrate
that's not my experience with it. Maybe I'm doing sth wrong. I have posted this question, hopefully you'd be able to help me out :)Gadabout

© 2022 - 2024 — McMap. All rights reserved.