Skip to content

Commit

Permalink
Document test exception handling in the User Guide
Browse files Browse the repository at this point in the history
This commit documents how exceptions in tests are handled by explaining
the following.

1. Uncaught exceptions cause test failures by default

2. Assertions are implemented using exceptions

3. JUnit 5 does not differentiate between failed assertions and other
   exceptions but IDEs and other tools may choose to do so by checking
   for instances of AssertionError

This commit also introduces dedicated examples for assertThrows(),
assertThrowsExactly(), and assertDoesNotThrow().

Closes #3780
  • Loading branch information
driptaroop authored and sbrannen committed Apr 20, 2024
1 parent 1e5de9e commit f915d67
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 0 deletions.
97 changes: 97 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,103 @@ NOTE: As of JUnit Jupiter 5.4, it is also possible to use methods from JUnit 4's
`AssumptionViolatedException` to signal that a test should be aborted instead of marked
as a failure.

[[writing-tests-exceptions]]
=== Exception Handling

JUnit Jupiter provides robust support for handling test exceptions. This includes the
built-in mechanisms for managing test failures due to exceptions, the role of exceptions
in implementing assertions, and how to specifically assert non-throwing conditions in code.

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

In JUnit Jupiter, if an exception is thrown and not caught within the test method, the
framework will mark the test as failed.

[source,java,indent=0]
----
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.

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
fails only if an exception is thrown unexpectedly or if an assertion fails.

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

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.

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: 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`.

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

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.

[[writing-tests-exceptions-expected-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.

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

[[writing-tests-exceptions-expected-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.

[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

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`
assertion can be used when you want to verify that a particular piece of code does not
throw any exceptions.

[source,java,indent=0]
----
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>>.

[[writing-tests-disabling]]
=== Disabling Tests

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.assertDoesNotThrow;

import org.junit.jupiter.api.Test;

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

class AssertDoesNotThrowExceptionDemo {

@Test
void assertDoesNotThrowExceptionTest() {
assertDoesNotThrow(() -> {
shouldNotThrowException();
});
}

void shouldNotThrowException(){}
}

// end::user_guide[]
// @formatter:on
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertThrows;

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");
});
}
}

// end::user_guide[]
// @formatter:on
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertThrowsExactly;

import org.junit.jupiter.api.Test;

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

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");
});
}
}

// end::user_guide[]
// @formatter:on
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 org.junit.jupiter.api.Test;

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

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();
}
}

// end::user_guide[]
// @formatter:on

0 comments on commit f915d67

Please sign in to comment.