JUnit 5: How to assert an exception is thrown? - java

Is there a better way to assert that a method throws an exception in JUnit 5?
Currently, I have to use an #Rule in order to verify that my test throws an exception, but this doesn't work for the cases where I expect multiple methods to throw exceptions in my test.

You can use assertThrows(), which allows you to test multiple exceptions within the same test. With support for lambdas in Java 8, this is the canonical way to test for exceptions in JUnit.
Per the JUnit docs:
import static org.junit.jupiter.api.Assertions.assertThrows;
#Test
void exceptionTesting() {
MyException thrown = assertThrows(
MyException.class,
() -> myObject.doThing(),
"Expected doThing() to throw, but it didn't"
);
assertTrue(thrown.getMessage().contentEquals("Stuff"));
}

In Java 8 and JUnit 5 (Jupiter) we can assert for exceptions as follows.
Using org.junit.jupiter.api.Assertions.assertThrows
public static < T extends Throwable > T assertThrows(Class< T > expectedType,
Executable executable)
Asserts that execution of the supplied executable throws an exception of the expectedType and returns the exception.
If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
If you do not want to perform additional checks on the exception instance, simply ignore the return value.
#Test
public void itShouldThrowNullPointerExceptionWhenBlahBlah() {
assertThrows(NullPointerException.class,
()->{
//do whatever you want to do here
//ex : objectName.thisMethodShoulThrowNullPointerExceptionForNullParameter(null);
});
}
That approach will use the Functional Interface Executable in org.junit.jupiter.api.
Refer :
http://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions
http://junit.org/junit5/docs/5.0.0-M2/api/org/junit/jupiter/api/Executable.html
http://junit.org/junit5/docs/5.0.0-M4/api/org/junit/jupiter/api/Assertions.html#assertThrows-java.lang.Class-org.junit.jupiter.api.function.Executable-

They've changed it in JUnit 5 (expected: InvalidArgumentException, actual: invoked method) and code looks like this one:
#Test
public void wrongInput() {
Throwable exception = assertThrows(InvalidArgumentException.class,
()->{objectName.yourMethod("WRONG");} );
}

Now Junit5 provides a way to assert the exceptions
You can test both general exceptions and customized exceptions
A general exception scenario:
ExpectGeneralException.java
public void validateParameters(Integer param ) {
if (param == null) {
throw new NullPointerException("Null parameters are not allowed");
}
}
ExpectGeneralExceptionTest.java
#Test
#DisplayName("Test assert NullPointerException")
void testGeneralException(TestInfo testInfo) {
final ExpectGeneralException generalEx = new ExpectGeneralException();
NullPointerException exception = assertThrows(NullPointerException.class, () -> {
generalEx.validateParameters(null);
});
assertEquals("Null parameters are not allowed", exception.getMessage());
}
You can find a sample to test CustomException here : assert exception code sample
ExpectCustomException.java
public String constructErrorMessage(String... args) throws InvalidParameterCountException {
if(args.length!=3) {
throw new InvalidParameterCountException("Invalid parametercount: expected=3, passed="+args.length);
}else {
String message = "";
for(String arg: args) {
message += arg;
}
return message;
}
}
ExpectCustomExceptionTest.java
#Test
#DisplayName("Test assert exception")
void testCustomException(TestInfo testInfo) {
final ExpectCustomException expectEx = new ExpectCustomException();
InvalidParameterCountException exception = assertThrows(InvalidParameterCountException.class, () -> {
expectEx.constructErrorMessage("sample ","error");
});
assertEquals("Invalid parametercount: expected=3, passed=2", exception.getMessage());
}

TL;DR: If you are on JUnit 5.8.0+ version, you can use assertThrowsExactly() instead of assertThrows() to match the exact exception type.
assertThrowsExactly(FileNotFoundException.class, () -> service.blah());
You can use assertThrows(), But with assertThrows your assertion will pass even if the thrown exception is of child type.
This is because, JUnit 5 checks exception type by calling Class.isIntance(..), Class.isInstance(..) will return true even if the thrown exception is of a child type.
The workaround for this is to assert on Class:
Throwable throwable = assertThrows(Throwable.class, () -> {
service.readFile("sampleFile.txt");
});
assertEquals(FileNotFoundException.class, throwable.getClass());

You can use assertThrows(). My example is taken from the docs http://junit.org/junit5/docs/current/user-guide/
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
....
#Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}

I think this is an even simpler example
List<String> emptyList = new ArrayList<>();
Optional<String> opt2 = emptyList.stream().findFirst();
assertThrows(NoSuchElementException.class, () -> opt2.get());
Calling get() on an optional containing an empty ArrayList will throw a NoSuchElementException. assertThrows declares the expected exception and provides a lambda supplier (takes no arguments and returns a value).
Thanks to #prime for his answer which I hopefully elaborated on.

An even simpler one liner. No lambda expressions or curly braces required for this example using Java 8 and JUnit 5
import static org.junit.jupiter.api.Assertions.assertThrows;
#Test
void exceptionTesting() {
assertThrows(MyException.class, myStackObject::doStackAction, "custom message if assertion fails...");
// note, no parenthesis on doStackAction ex ::pop NOT ::pop()
}

Actually I think there is a error in the documentation for this particular example. The method that is intended is expectThrows
public static void assertThrows(
public static <T extends Throwable> T expectThrows(

My solution:
protected <T extends Throwable> void assertExpectedException(ThrowingRunnable methodExpectedToFail, Class<T> expectedThrowableClass,
String expectedMessage) {
T exception = assertThrows(expectedThrowableClass, methodExpectedToFail);
assertEquals(expectedMessage, exception.getMessage());
}
And you can call it like this:
assertExpectedException(() -> {
carService.findById(id);
}, IllegalArgumentException.class, "invalid id");

This is what I do when testing to make sure an exception has been thrown
//when
final var tripConsumer = new BusTripConsumer(inputStream);
final Executable executable = () -> tripConsumer.deserialiseTripData();
//then
assertThrows(IllegalArgumentException.class, executable);

Here is an easy way.
#Test
void exceptionTest() {
try{
model.someMethod("invalidInput");
fail("Exception Expected!");
}
catch(SpecificException e){
assertTrue(true);
}
catch(Exception e){
fail("wrong exception thrown");
}
}
It only succeeds when the Exception you expect is thrown.

Related

Spring Webflux - Proper way to throw checked custom exception (not RuntimeException)

May I ask what is the proper way to throw checked custom exception in Spring webflux please?
I would like to insist, it is about checked custom exception, like MyException.java, not something like RuntimeException, and it is about throwing exception, not handling exception.
I tried the following :
#Controller
#SpringBootApplication
public class QuestionHowToThrowException {
public static void main(String[] args) {
SpringApplication.run(QuestionHowToThrowException.class);
}
#PostMapping(path = "/question", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<QuestionResponse>> question(#RequestBody QuestionRequest questionRequest) {
Mono<FirstStep> firstStepMono = WebClient.create().post().uri("http://firstWebService:8111/getFirstStep")
.body(questionRequest.getThing(), String.class).retrieve().bodyToMono(FirstStep.class);
Mono<SecondStep> secondStepMono = firstStepMono.map(oneFirstStep -> getSecondStepFromFirstStepAfterCheck(oneFirstStep));
return secondStepMono.map(oneSecondStep -> ResponseEntity.ok(new QuestionResponse(oneSecondStep.getSecondThing())));
}
private SecondStep getSecondStepFromFirstStepAfterCheck(FirstStep firstStep) throws MyException {
if (firstStep.getThingNeedsToCheckCanThrowException().equals("exception")) {
throw new MyException("exception");
} else {
return new SecondStep(firstStep.getThingNeedsToCheckCanThrowException() + "good");
}
}
public class QuestionRequest {
private String thing;
public String getThing() {
return thing;
}
}
public class QuestionResponse {
private String response;
public QuestionResponse(String response) {
this.response = response;
}
}
public class FirstStep {
private String thingNeedsToCheckCanThrowException;
public String getThingNeedsToCheckCanThrowException() {
return thingNeedsToCheckCanThrowException;
}
}
public class SecondStep {
private String secondThing;
public SecondStep(String secondThing) {
this.secondThing = secondThing;
}
public String getSecondThing() {
return secondThing;
}
}
}
This is not possible, since there in an unhandled exception in getSecondStepFromFirstStepAfterCheck method.
If I throw and propagate, private SecondStep getSecondStepFromFirstStepAfterCheck(FirstStep firstStep) throws MyException the lambda caller method is not happy.
What is the cleanest and proper way to throw custom exception in webflux please?
Thank you
Reading through your sample code, it looks like you are trying to introduce some error handling with on your Mono.
You can create an unchecked exception by extending the RuntimeException class. If you want a checked exception that enforces handling, you can simply extend Exception.
public class MyException extends RuntimeException {
public MyException(String msg) {
super(s);
}
}
The cleanest way to throw an exception with the Reactor project is really just to throw it. There are error handling functions that allow you to provide different flows to certain error cases.
The good news is you have several options that provides some flow control for error handling.
Project Reactor provides several of these methods on the Mono object.
doOnError(),onErrorContinue(),onErrorReturn(),onErrorStop(),onErrorMap()
I am not entirely sure what you are trying to achieve with the following sample code.
return Mono.error(new MyException("exception"));
} else {
return Mono.just(new SecondStep(firstStep.getThingNeedsToCheckCanThrowException() + "good"));
But this looks like a good case for a onErrorMap() since it looks like you are trying to translate some exception here
return Mono.just(new SecondStep(firstStep.getThingNeedsToCheckCanThrowException() + "good")
.onErrorMap(e -> "translated result");
For our applications, we have our custom base exception extend from RuntimeException. We then have standard exception handling that looks for our custom exception for special handling before returning results back to the end user. This allows us to use normal throws mechanics since we want all exceptions thrown to ripple up the top level of the call.
For performance concerns webflux and reactive are slightly lower performance on a per call basis especially for calls that don't need to do any parallelization. However once load is put onto the system it tends to become more performant primarily related to garbage collection. Overhead from the difference between map and flatMap should be negligible at best.

Argument(s) are different! Wanted:

I am writing a unit test for my below code
public class Class1 {
protected void execute(String a, String b) {
try{
process(a,b);
}
catch(Exception E){
Class2.write(e,Class1.class.getSimpleName())
}
}
private void process(String a, String b) {
validate(a,b);
// Doing some processing on a and b values
}
private void validate (String a, String b) {
if(a==null || a.isEmpty() || b==null || b.isEmpty())
throw new IllegalArgumentException("Input value cannot be null or empty");
}
}
For the above code, I am trying to write a UT which covers the exception use case. Below is my UT code,
#Test
public void test1(){
try {
PowerMockito.mockStatic(Class2.class);
PowerMockito.when(Class2.class, "write", Mockito.anyObject(), Mockito.anyString())
.thenCallRealMethod();
Class1 class1 = new Class1();
Class2.write(new IllegalArgumentException("Input value cannot be null or empty"),Class1.class.getSimpleClassName());
PowerMockito.verifyStatic(Class2.class, VerificationModeFactory.times(1));
class1.execute(Mockito.anyString(),Mockito.anyString());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
I am getting the below exception when I execute the above test
Argument(s) are different! Wanted:
Class2.write{
java.lang.IllegalArgumentException:Input value cannot be null or empty,
Class1
}
Actual invocation has different arguments:
Class2.write{
java.lang.IllegalArgumentException:Input value cannot be null or empty,
Class1
}
Can someone please help me on resolving this issue?
I really appreciate your help and time
Thanks in Advance
Your Problem:
IllegalArgumentException does not use the string message for equality. It would be safer to test the string message or the class type. I would prefer that the test detect the type rather than the message, as the string message should not be used for control flow, it is an implementation detail.
System.out.println(Objects.equals(
new IllegalArgumentException(),
new IllegalArgumentException()));
// false
System.out.println(Objects.equals(
new IllegalArgumentException().getClass(),
new IllegalArgumentException().getClass()));
// true
So to mock this I would use matchers:
any(IllegalArgumentException.class), eq(Class1.class.getSimpleName())
Issues with your design:
I'm going to end with an argument against how this code is structured, being that it is not built around dependency injection. Rather than calling the static method Class2::write, you could be calling an instance method.
For example, create the interface:
public interface Writer {
void write(Exception e, String source);
}
You can now refactor the class to provide two ctors, one that accepts any writer, and one that defaults to Class2.
public class Class1 {
private final Writer writer;
public Class1() {
this(Class2::write);
}
public Class1(Writer writer) {
this.writer = writer;
}
protected void execute(String a, String b) {
try {
process(a,b);
}
catch (Exception E) {
writer.write(e, Class1.class.getSimpleName());
}
}
...
}
Using this strategy you can now simply create an instance mock of Writer. This avoids having to mock as static method which changes the bytecode of your application, and also make your class more flexible as it can support many different writer implementations now. Anything that is modifying the bytecode of the application should be used very sparingly, such as replacing static method calls, does not truly validate the runtime execution of your code.
In my opinion, the majority of the PowerMockito/PowerMock only help verify code which was not built with testability / flexibility in mind. You shouldn't need to use anything outside of the Mockito/EasyMock tool-set for well structured code. There are some exceptions but the tool-set should be used very sparingly.

How to verify that static method throws an exception using AssertJ?

When I try to test this method
static void validatePostcode(final String postcode, final String addressLine)
{
if(! hasValidPostcode(postcode, addressLine)) {
throw new InvalidFieldException("Postcode is null or empty ");
}
}
using the following test
#Test
public void testThrowsAnException()
{
assertThatThrownBy(validatePostcode("", "")).isInstanceOf(InvalidFieldException.class);
}
I get this error message in IntelliJ
assertThatThrownBy (org.assertj.core.api.ThrowableAssert.ThrowingCallable) in Assertions cannot be applied to (void)
 
Same thing with assertThatExceptionOfType.
Is it possible to test that static method actually throws an unchecked exception using AssertJ? What should I change in my test?
As the compilation error demonstrates, that method expects a throwing callable.
#Test
public void testThrowsAnException()
{
assertThatThrownBy(() -> validatePostcode("", "")).isInstanceOf(InvalidFieldException.class);
}
change to this way. you need to pass lambda to test with assertj
assertThatThrownBy(()->validatePostcode("","")).isInstanceOf(InvalidFieldException.class);

Create unit test for unlikely scenario and is it worth the trouble?

For below method, is there a way to create unit test to cause DatatypeConfigurationException, so I can test that it threw ConversionException?
Here is my code:
public static XMLGregorianCalendar getXMLGregorianCalendar(final LocalDate localDate) {
XMLGregorianCalendar xmlGregorianCalendar = null;
if (localDate != null) {
final String dateString = localDate.format(yyyMMddFormat);
try {
xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(dateString);
} catch (DatatypeConfigurationException e) {
throw new ConversionException("Unable to format LocalDate.", e);
}
}
return xmlGregorianCalendar;
}
You can override the implementation that the factory will create by setting a system property with a classname to instantiate. Then that class can throw an exception in that method.
For example like this
public class FailingDatatypeFactory implements DatatypeFactory {
public XMLGregorianCalendar newXMLGregorianCalendar() { throw new DatatypeConfigurationException() }
}
and then set it up like so
System.setProperty("javax.xml.datatype.DatatypeFactory", FailingDatatypeFactory.class.getName());
Now after you run your test case you should clear the property so no other tests try to instantiate that implementation.
I just had to do this for my unit tests. All you need to do is set the Sys prop javax.xml.datatype.DatatypeFactory to something invalid. It will throw the exception when it tries to find the class that doesn't exist. Just be sure to clear the prop after your test.
Here the checked exception is thrown by javax.xml.datatype.DatatypeFactory.newInstance().
It is a static method. So you cannot mock it straightly.
1) As alternative you could try to find the scenario that could provoke the exception to be risen.
Let's go. The exception is throw here :
private static <T> T findServiceProvider(final Class<T> type)
throws DatatypeConfigurationException{
try {
return AccessController.doPrivileged(new PrivilegedAction<T>() {
public T run() {
final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
final Iterator<T> iterator = serviceLoader.iterator();
if (iterator.hasNext()) {
return iterator.next();
} else {
return null;
}
}
});
} catch(ServiceConfigurationError e) {
final DatatypeConfigurationException error =
new DatatypeConfigurationException(
"Provider for " + type + " cannot be found", e);
throw error;
}
}
So DatatypeConfigurationException is thrown when ServiceConfigurationError is thrown and caught. But ServiceConfigurationError is an error and not an exception.
Trying to simulate an error becomes very hacky.
2) Other alternative to test it : wrapping DatatypeFactory.newInstance() in an instance of your own class.
In this way you can mock it without difficulty :
public class DataTypeFactoryWrapper {
public DatatypeFactory newInstance(){
return DatatypeFactory.newInstance();
}
}
Now change your code in this way :
private DataTypeFactoryWrapper dataTypeFactoryWrapper;
//...
xmlGregorianCalendar = dataTypeFactoryWrapper.newInstance().newXMLGregorianCalendar(dateString);
Now you can mock dataTypeFactoryWrapper in your test class.
3) Last alternative : don't test it. Consider it as it is, that is an Error wrapper and Error are hard/tricky to test.
Whatever the javadoc explains that :
An Error is a subclass of Throwable that indicates serious problems
that a reasonable application should not try to catch. Most such
errors are abnormal conditions
You can either:
Mock static method (DatatypeFactory.newInstance()) using PowerMock for example and set it up to throw DatatypeConfigurationException. Then in unit test check that this exception is wrapped by ConversionException.
Since I'm not a big fan of mocking static methods - I would create new component - say XmlGregorianCalendarProvider (which will use DatatypeFactory.newInstance().newXMLGregorianCalendar(dateString) internally) and mock it instead using standard mocking mechanism (e.g. JUnit).
Still then in unit test check that this exception is wrapped by ConversionException.

Java unit test with common code

I am new to Java so apologies in advance if this is a silly question.
I noticed that I'm writing redundant code as my test cases are testing similar behavior with different attributes of same object.
#Test
public void testInvalidA() {
obj.setA(null)
//verify exception thrown if A is null
}
#Test
public void testInvalidB() {
obj.setB(null)
//verify exception thrown if B is null
}
Is there a way that I could simplify this?
Thanks!
You can use assertj. Example code would be like this,
#Test
public void testObjectsAreValid() {
assertThatExceptionOfType(ExpectedException.class)
.isThrownBy(obj.setA(null);
assertThatExceptionOfType(ExpectedException.class)
.isThrownBy(obj.setB(null);
}
assertj provides direct methods for some common exceptions.
assertThatIllegalArgumentException().isThrownBy(() -> obj.setB(null));
Check here for more documentation.
I agree with Dawood though, keep each test separate and clear.
There's nothing wrong with the way you were doing it -- in fact its preferable most of the time. But if your example is a simplification of complexity that repeats with a similar testable signature, you could try a bit of functional programming:
private void nullNotAllowed( Consumer<Object> method ) {
try {
method.accept( null );
fail( "Null Not Allowed");
}
catch ( Exception e ) { /*pass*/ }
}
#Test public void nonNullableSetters() {
YourClass subject = new YourClass();
nullNotAllowed( subject::setA );
nullNotAllowed( subject::setB );
}

Categories