-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Hello,
I have encountered a subtle problem with the assertThrows API.
The API accepts an Executable, which conceptually is a void invocation run internally by JUnit, and an exception of a specified type is expected to be thrown, and thus caught. There is really no need to keep any returned value from the operation encapsulated by the Executable, because it is expected to throw.
However, there are times when some attention should be given to a returned value, if the assertion that it throws an exception actually fails, and the Executable manages to successfully execute: methods returning InputStream, OutputStream, and really any instance of AutoCloseable.
Say you have a method with the following signature:
InputStream getData();And you are testing an error scenario like this:
assertThrows(UnableToGiveDataException.class, () -> getData());In the case of this test failing (i.e. getData() successfully executes with no exception), the returned InputStream will not be closed, and this may introduce problems for any remaining tests in a suite.
One solution to this is of course to not use assertThrows with methods returning an AutoCloseable instance. But this is not evident when one knows and appreciates the assertThrows-methods, as there is nothing preventing you from using it, and it is admittedly a bit subtle why one should avoid this. It may not manifest as an actual problem in your particular test suite.
Would it be a viable solution to adapt the API to offer overloads of assertThrows which accept for instance a ThrowingSupplier<T>, so JUnit can actually access the returned value in the case of a failing assert? JUnit could then inspect the returned value to see if it is an AutoCloseable, and in that case invoke close(), and then finally throw the appropriate AssertionError because the expected exception was not thrown. This should also enable generating more informative test failure messages, as assertThrows(..) can then fail with information about the value which was returned instead of an expected exception being thrown.
What are your thought on this? 🙂