Skip to content

Commit

Permalink
Revise "Exception Handling" section of User Guide
Browse files Browse the repository at this point in the history
See #3780
  • Loading branch information
sbrannen committed Apr 20, 2024
1 parent f915d67 commit 27f3b7c
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 104 deletions.
84 changes: 48 additions & 36 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ built-in mechanisms for managing test failures due to exceptions, the role of ex
in implementing assertions, and how to specifically assert non-throwing conditions in code.

[[writing-tests-exceptions-uncaught]]
==== Uncaught Exceptions and Test Failures
==== Uncaught Exceptions

In JUnit Jupiter, if an exception is thrown and not caught within the test method, the
framework will mark the test as failed.
Expand All @@ -385,74 +385,85 @@ framework will mark the test as failed.
include::{testDir}/example/exception/UncaughtExceptionHandlingDemo.java[tags=user_guide]
----

In the above example, the `testThatFailsDueToUncaughtException` method throws a
`RuntimeException`. Since the exception is not caught within the test method, JUnit
Jupiter will automatically fail the test.
In the above example, the `failsDueToUncaughtException()` method throws an
`ArithmeticException`. Since the exception is not caught within the test method, JUnit
Jupiter will mark the test as failed.

NOTE: It's important to clarify that specifying a `throws` clause in the test method
has no effect on the outcome of the test. JUnit Jupiter does not interpret it as
an expectation or assertion about what exceptions the test method should throw. A test
NOTE: It's important to clarify that specifying a `throws` clause in the test method has
no effect on the outcome of the test. JUnit Jupiter does not interpret a `throws` clause
as an expectation or assertion about what exceptions the test method should throw. A test
fails only if an exception is thrown unexpectedly or if an assertion fails.

[[writing-tests-exceptions-assertions]]
==== Assertions Using Exceptions
[[writing-tests-exceptions-failed-assertions]]
==== Failed Assertions

Assertions in JUnit Jupiter are implemented using exceptions. The framework provides
a set of assertion methods in the `org.junit.jupiter.api.Assertions` class, which throw
Assertions in JUnit Jupiter are implemented using exceptions. The framework provides a set
of assertion methods in the `org.junit.jupiter.api.Assertions` class, which throw
`AssertionError` when an assertion fails. This mechanism is a core aspect of how JUnit
handles assertion failures as exceptions.
handles assertion failures as exceptions. See the <<writing-tests-assertions>> section for
further information about JUnit Jupiter's assertion support.

NOTE: This failed assertion behaviour (i.e. throwing `AssertionError`) may be different
for third party assertion libraries. See also: <<writing-tests-assertions-third-party>>.
NOTE: Third-party assertion libraries may choose to throw an `AssertionError` to signal a
failed assertion; however, they may also choose to throw different types of exceptions to
signal failures. See also: <<writing-tests-assertions-third-party>>.

NOTE: JUnit Jupiter itself does not differentiate between failed assertions
TIP: JUnit Jupiter itself does not differentiate between failed assertions
(`AssertionError`) and other types of exceptions. All uncaught exceptions lead to a test
failure. However, Integrated Development Environments (IDEs) and other tools may
distinguish between these two types of failures by checking whether the thrown exception
is an instance of `AssertionError`.

[source,java,indent=0]
----
include::{testDir}/example/exception/FailedAssertionDemo.java[tags=user_guide]
----

In the above example, the `failsDueToUncaughtAssertionError()` method throws an
`AssertionError`. Since the exception is not caught within the test method, JUnit Jupiter
will mark the test as failed.

[[writing-tests-exceptions-expected]]
==== Asserting Expected Exception Conditions
==== Asserting Expected Exceptions

JUnit Jupiter offers specialized assertions for testing that specific exceptions are
thrown under expected conditions. The `assertThrows` and `assertThrowsExactly` assertions
are critical tools for validating that your code responds correctly to error conditions
by throwing the appropriate exceptions.
thrown under expected conditions. The `assertThrows()` and `assertThrowsExactly()`
assertions are critical tools for validating that your code responds correctly to error
conditions by throwing the appropriate exceptions.

[[writing-tests-exceptions-expected-assertThrows]]
===== Using `assertThrows`
===== Using `assertThrows()`

The `assertThrows` method is used to verify that a particular type of exception is thrown
during the execution of a provided executable block. It not only checks for the type of
the thrown exception but also its subclasses, making it suitable for more generalized
exception handling tests. The `assertThrows` assertion method returns the thrown
exception object to allow performing additional assertions on it.
The `assertThrows()` method is used to verify that a particular type of exception is
thrown during the execution of a provided executable block. It not only checks for the
type of the thrown exception but also its subclasses, making it suitable for more
generalized exception handling tests. The `assertThrows()` assertion method returns the
thrown exception object to allow performing additional assertions on it.

[source,java,indent=0]
----
include::{testDir}/example/exception/ExceptionAssertionDemo.java[tags=user_guide]
----

[[writing-tests-exceptions-expected-assertThrowsExactly]]
===== Using `assertThrowsExactly`
===== Using `assertThrowsExactly()`

`assertThrowsExactly` is used when you need to assert that an exception is not only
thrown but is exactly of a specific type, not allowing for subclasses of the exception.
This is useful when precise exception handling behavior needs to be validated. Similar
to `assertThrows`, `assertThrowsExactly` assertion method also returns the thrown
exception object to allow performing additional assertions on it.
The `assertThrowsExactly()` method is used when you need to assert that the exception
thrown is exactly of a specific type, not allowing for subclasses of the expected
exception type. This is useful when precise exception handling behavior needs to be
validated. Similar to `assertThrows()`, the `assertThrowsExactly()` assertion method also
returns the thrown exception object to allow performing additional assertions on it.

[source,java,indent=0]
----
include::{testDir}/example/exception/ExceptionAssertionExactDemo.java[tags=user_guide]
----

[[writing-tests-exceptions-using-assertDoesNotThrow]]
==== Using `assertDoesNotThrow` to Verify Absence of Exceptions
[[writing-tests-exceptions-not-expected]]
==== Asserting That no Exception is Expected

Although any exception thrown from a test method will cause the test to fail, there are
certain use cases where it can be beneficial to explicitly assert that an exception is
not thrown for a given code block within a test method. The `assertDoesNotThrow`
_not_ thrown for a given code block within a test method. The `assertDoesNotThrow()`
assertion can be used when you want to verify that a particular piece of code does not
throw any exceptions.

Expand All @@ -461,8 +472,9 @@ throw any exceptions.
include::{testDir}/example/exception/AssertDoesNotThrowExceptionDemo.java[tags=user_guide]
----

NOTE: Other third party assertion libraries also provide similar support. For example,
`AssertJ` has `assertThatNoException().isThrownBy(() -> ...)`. See also: <<writing-tests-assertions-third-party>>.
NOTE: Third-party assertion libraries often provide similar support. For example, AssertJ
has `assertThatNoException().isThrownBy(() -> ...)`. See also:
<<writing-tests-assertions-third-party>>.

[[writing-tests-disabling]]
=== Disabling Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,18 @@

import org.junit.jupiter.api.Test;

// @formatter:off
// tag::user_guide[]

class AssertDoesNotThrowExceptionDemo {

// tag::user_guide[]
@Test
void assertDoesNotThrowExceptionTest() {
void testExceptionIsNotThrown() {
assertDoesNotThrow(() -> {
shouldNotThrowException();
});
}

void shouldNotThrowException(){}
}
void shouldNotThrowException() {
}
// end::user_guide[]

// end::user_guide[]
// @formatter:on
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,30 @@

import org.junit.jupiter.api.Test;

// @formatter:off
// tag::user_guide[]

class ExceptionAssertionDemo {

@Test
void testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion throws
// the expected IllegalArgumentException
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception messages
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("Expected exception");
});
assertEquals("Expected exception", exception.getMessage());

// The following assertion should also succeed because the code under assertion throws
// IllegalArgumentException which is subclass of RuntimeException
assertThrows(RuntimeException.class, () -> {
throw new IllegalArgumentException("Expected exception");
});
}
}
// @formatter:off
// tag::user_guide[]
@Test
void testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion
// throws the expected IllegalArgumentException.
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception message.
IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("expected message");
});
assertEquals("expected message", exception.getMessage());

// The following assertion also succeeds because the code under assertion
// throws IllegalArgumentException which is a subclass of RuntimeException.
assertThrows(RuntimeException.class, () -> {
throw new IllegalArgumentException("expected message");
});
}
// end::user_guide[]
// @formatter:on

// end::user_guide[]
// @formatter:on
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,32 @@

import org.junit.jupiter.api.Test;

// @formatter:off
// tag::user_guide[]
import extensions.ExpectToFail;

public class ExceptionAssertionExactDemo {

// end::user_guide[]
@extensions.ExpectToFail
// tag::user_guide[]
@Test
void testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion throws
// IllegalArgumentException which is exactly equal to the expected type
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception messages
IllegalArgumentException exception = assertThrowsExactly(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("Expected exception");
});
assertEquals("Expected exception", exception.getMessage());

// The following assertion should fail because the assertion expected exactly
// RuntimeException to be thrown, not subclasses
assertThrowsExactly(RuntimeException.class, () -> {
throw new IllegalArgumentException("Expected exception");
});
}
}
@ExpectToFail
// @formatter:off
// tag::user_guide[]
@Test
void testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion throws
// IllegalArgumentException which is exactly equal to the expected type.
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception message.
IllegalArgumentException exception =
assertThrowsExactly(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("expected message");
});
assertEquals("expected message", exception.getMessage());

// The following assertion fails because the assertion expects exactly
// RuntimeException to be thrown, not subclasses of RuntimeException.
assertThrowsExactly(RuntimeException.class, () -> {
throw new IllegalArgumentException("expected message");
});
}
// end::user_guide[]
// @formatter:on

// end::user_guide[]
// @formatter:on
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package example.exception;

import static org.junit.jupiter.api.Assertions.assertEquals;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

import extensions.ExpectToFail;

class FailedAssertionDemo {

// tag::user_guide[]
private final Calculator calculator = new Calculator();

// end::user_guide[]

@ExpectToFail
// tag::user_guide[]
@Test
void failsDueToUncaughtAssertionError() {
// The following incorrect assertion will cause a test failure.
// The expected value should be 2 instead of 99.
assertEquals(99, calculator.add(1, 1));
}
// end::user_guide[]

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,27 @@

package example.exception;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

// @formatter:off
// tag::user_guide[]
import extensions.ExpectToFail;

class UncaughtExceptionHandlingDemo {

// end::user_guide[]
@extensions.ExpectToFail
// tag::user_guide[]
@Test
void failsDueToUncaughtException() {
// the following thrown exception will cause a test failure
throw new RuntimeException();
}
}
// tag::user_guide[]
private final Calculator calculator = new Calculator();

// end::user_guide[]

// end::user_guide[]
// @formatter:on
@ExpectToFail
// tag::user_guide[]
@Test
void failsDueToUncaughtException() {
// The following throws an ArithmeticException due to division by
// zero, which causes a test failure.
calculator.divide(1, 0);
}
// end::user_guide[]

}

0 comments on commit 27f3b7c

Please sign in to comment.