0

I know there are a number of questions on this topic, but all of them seem to assume one of two things:

  1. You just want to test if an exception was thrown and not caught,
  2. You should test the function that is inside of the try block directly

I'm not sure how I can apply those options to this case. I have a small try/catch block, like so:

try {
    o.getDataContainer().build(...);
    o2.setDataContainer(o.getDataContainer());
} catch (final Exception e) {
    LOGGER.error("Data set failed", e);
}

As you can see, if o.getDataContainer() returns null, an exception would be triggered. However, that exception is then caught, and the test tool considers it a successful test. Is it possible to test that the exception occurred without changing the code?

I ask because our logging system triggers a trouble ticket if it picks up an exception that is caught and logged. Since it is common human error to forget to guard something like this, I would like to write UTs that can test if an exception was triggered and caught. I can't remove the whole-program protection provided by the catch block, but the error would also cause a degradation of the user experience, since the data isn't being passed along. (I work in a place where minutes of site downtime equal millions of dollars lost.)

In other words: The exception is an error, and we want to log it and investigate it, but in case this manages to trigger on prod, we don't want to risk the whole site going down.

Note: This try/catch sits inside a much larger function with many such try/catch blocks. One could easily argue bad overall design, but fixing it is not an option (without a huge amount of free dev time, at least).

Update: As the task at hand does not allow me to spend a great deal of time on this, I went with a very simple generic test that would fail if the guard and catch were both removed so that I could move on. But, I'm leaving the question unanswered for now in hopes of continuing conversation. I would love to be able to write a simple UT for each new feature that fails if any exceptions are triggered and caught.

Eric Shields
  • 145
  • 2
  • 13
  • 2
    I wouldn't argue that it's bad design. It _is_ bad design. It's a bit concerning that the entire site is dependent on this single line of code. Surely that's just a single session? Exceptions aren't bad. It's better to have an exception than to let the user resume in a broken session. (I know this doesn't answer your question. Off my soap box :-)) – Christopher Schneider May 08 '17 at 20:44
  • Do you use Mockito for unit testing? Mock `LOGGER` and then you can `verify(LOGGER).error(eq("Data set failed"), any(NullPointerExeption.class));` – Andrew S May 08 '17 at 20:49
  • @ChristopherSchneider It is a modular system, so I am sure there is a catch up the way somewhere that would prevent the whole site going down, true. But let's say it was an issue that would trigger regardless of session, which the recently found bug that prompted this would be; I would much rather limit the damage to my data set, rather than break a bunch of others too. Better to break 1% of the site than 10%, or even 2%, right? Edit: I should also note that catching this exception instead of letting it bubble is very similar to graceful degradation - the UI will just miss one tiny bit. – Eric Shields May 08 '17 at 20:57

2 Answers2

0

Ignoring the issues with this code (Sometimes you've gotta put lipstick on a pig, I guess), this is how I might handle the situation.

I'd use Mockito and mock o2, then use an Answer to ensure the method is invoked.

A test might look like this:

@RunWith(MockitoJUnitRunner.class)
public class TestClass{
    @Mock
    O2 o2;
    @Mock
    O1 o1;

    boolean exceptionThrown = false;

    @Test
    public void test(){
        Mockito.doAnswer(new Answer<Void>(){     
           public Void answer(InvocationOnMock invocation) throws Throwable {
              exceptionThrown = true; 
           throw new RuntimeException("some message");
        }            
      }).when(o2).setDataContainer(DataContainer.class);
    }
}

Essentially, you can Mock out o2 in your example, and force the exception.

If this doesn't quite do what you want, you may need to mock LOGGER and verify that it's invoked with LOGGER.error("some message");. Unfortunately, mocking statics is not at all elegant, but it can be done with PowerMock.

Christopher Schneider
  • 3,745
  • 2
  • 24
  • 38
  • I am unfamiliar with this structuring with mocks and variables external to the testcase. Most of our test files are a heavy with legacy, and the extent of annotations used are Before (on the setUp function) and Test. Can this be modified to exist within a test case? Also, to see if I understand this structure: You're telling it to run the anonymous function when o2.setDataContainer() is called? The structuring here is very different from the "Mockito.when()" I'm used to. Sadly as mentioned on the other answer, LOGGER is private static final. – Eric Shields May 09 '17 at 20:06
  • This is essentially the same as `when`, but instead of simply returning an object, you can invoke a method. You don't have to use the annotations, but they make things cleaner. You can just use `Mockito.mock`. I'm not sure what you mean by "within a test case." Just write a new test. – Christopher Schneider May 09 '17 at 20:13
  • Basically I meant putting the things you have listed there as global to the class in a single test case instead (as the giant build function has a single giant test class too, surprise surprise). So long as there's no adverse affects to that, then I'll definitely give this a shot! – Eric Shields May 10 '17 at 23:36
0

You could add a custom handler to LOGGER that just throws when an error is logged. For java.util.logging you could do something like:

LOGGER.addHandler(new Handler() {
    public void publish(LogRecord record) {
        if ("Data set failed".equals(record.getMessage())) {
            throw new RuntimeException(record.getThrown());
        }
    }
    public void flush() {}
    public void close() throws SecurityException {}
});

I think log4j calls it "Appender," but the same principle should work. See How to create a own Appender in log4j? or How to Create a Custom Appender in log4j2?

Sean Van Gorder
  • 3,393
  • 26
  • 26
  • This looks like it would be a change in the code, rather than in the Unit Test, right? If so, it's something to keep in mind but would require much larger signoff than my task warrants. If it's from the UT, then I sadly don't have access to the LOGGER instance (it's private static final). – Eric Shields May 09 '17 at 19:57
  • No, this would be run from the unit test code. You can use reflection to access the private field: https://stackoverflow.com/questions/1196192/how-do-i-read-a-private-field-in-java – Sean Van Gorder May 09 '17 at 20:45
  • Thanks for the great link! I come from a coding world where reflection does not exist so never would have thought of it. – Eric Shields May 10 '17 at 23:43