0

Is it possible to @Test if an appropriate exception was thrown in the main code function eaven if it was catch in a try / catch block?

ex:

public int maxSum(int a, int b) {
    try {
        if (a + b > 100)
            throw new TooMuchException("Too much! Sum reduced to 50");
    } catch (TooMuchException e) {
        System.out.println(e);
        return 50;
    }
    return a + b;
} 

To be tested by somethink like this

@Test
void maxSum_TooMuchExceptionThrowedAndCatchedWhenSumOfNumbersIsOver100() {
    Service service = new Service();
    assertThatThrownBy(() -> {
        service.maxSum(55, 66);
    }).isInstanceOf(TooMuchException.class)
            .hasMessageContaining("Too much! Sum reduced to 50");
}

.

public class TooMuchException extends Throwable {
    public TooMuchException(String message) {
        super(message);
        }
}

Test Exception not the message.

I care about this because I want to be able to catch exceptions in function without crashing the program.

Pewu
  • 55
  • 6
  • 1
    In this situation, you just need to make sure that the method will return 50. It will be the correct test because there are not other cases in the if block and TooMuchException will be to handle – Dmitrii B Jan 09 '22 at 14:34
  • I believe in mockito one may mock TooMuchException, and track its constructor. – Joop Eggen Jan 09 '22 at 14:34
  • @Dmitrii Bykov I can test the returned value, and I know that will reflect the nature of the test, but I was thinking if it is possible to detect the exception thrown internally. – Pewu Jan 09 '22 at 14:42
  • @Joop Eggen I have not used Mock yet, I will have to read about it and about following the constructor – Pewu Jan 09 '22 at 14:44
  • I hope it will never be a part of production code when silently reducing the max value to 50 (why not 100 by the way)? 1) It's usually bad practice to throw/catch exceptions locally. You don't even need `try`/`catch`/`throw` here removing them all in favor of simple-stupid `if`/`else`. 2) Since the `catch` block actually handles the exception itself, you cannot (I believe) test it outside. What I would naively do in this case is supplying a mockable strategy that could provide a value if the sum exceeds the limits (hence you could pass a mock to the unit). – terrorrussia-keeps-killing Jan 09 '22 at 14:51
  • try/catch section handles an exception, you can't check that besides checking result try/catch block – Dmitrii B Jan 09 '22 at 14:51
  • 3) I would personally not use Hamcrest (especially for such simple cases like this one). – terrorrussia-keeps-killing Jan 09 '22 at 14:52
  • I gave a simple example so that I could easily grasp the meaning of what I would like to achieve. @fluffy It is not part of the production code but a simple example from which I would like to learn something. And now I know that you have to base on what comes out of the catch – Pewu Jan 09 '22 at 15:04
  • @fluffy It's usually bad practice to throw/catch exceptions locally. // Then where should i use it? – Pewu Jan 09 '22 at 15:10
  • @Pewu Exceptions are meant to tell the caller code that something went wrong (hence your unit test code could catch the exception for example and really test it) and not serve local method control flow. If your single method can both throw and catch the same exception, then it (most likely) can be simplified sacrificing no readability and no performance. – terrorrussia-keeps-killing Jan 09 '22 at 15:16
  • 1
    Even if it is possible, don't do that. Your implementation will be concrete as cement. Your unit test is not testing the requirements, it is testing the implementation. – Thomas Weller Jan 09 '22 at 16:00

1 Answers1

2

The test's job is to confirm that the code's behavior is as expected, which means: the code does what it needs to do. Tests should not verify implementation details.

Your exception being thrown and caught is an implementation detail because the outside program doesn't know or care about it. In this case it would be very reasonable to look at this code and conclude that throwing an exception here is useless (or badly performing, because creating a stack trace has a cost) and it should be removed. Yet if you add code in the test to check for the exception somehow, say by reading the stack trace written to stdout (very do-able if you know the io classes, just fiddly and brittle), then you will have wasted your time implementing that check and you will also make extra work for the task of changing the implementation. Somebody has to look at it, understand it, and check that it's safe to remove it.

That is why we try to avoid testing implementation details, there is always the chance we will have a defect or performance issue or design change that requires modifying our implementation. We can't always avoid rewriting the test, sometimes part of the problem is that the contract was not defined right or circumstances' changing have made it irrelevant. But if we can have the test check behavior only, then we have a better chance of bring able to write a new implementation and run it against the old test, verifying that the new version does what the old one did. Updating tests is work and we want to minimize our workload when maintaining tests.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276