How do you test that a method caught an exception? What if that catch did not have a side effect, it just logged output, or simply swallowed the exception?
Context
We have a method in a class that was written with a try catch and we need to unit test it. There are many ways this task can occur, such as the need to test legacy code or the need to write a test before a refactoring of such code.
We won’t go into the anti-pattern aspects or what constitutes proper handling of an exception here.
In a catch block there are three common ways of handling an exception:
try{ ... stuff ... }catch(X){ // 1. do something here // 2. maybe throw X or something else // 3. skip the above and do nothing }
How are these tested?
When the method reacts by throwing an exception we can test using the standard JUnit @Test(expected=SomeException.class), or for fine-grained verification, the test itself has a try catch block where we can assert the exception details.
If a method does nothing in the catch block, which is also called “swallowing” the exception, should it even be a test issue? Yes. The method is just tested normally. One of the tests must force the exception, of course. We do this since in future the method may be changed to handle the exception differently.
It gets more interesting when the catch block has ‘behavior’. This behavior has side effects. If these side effects are only local to the method, such as setting a flag false, then normal testing is adequate. If this behavior has side effects at the class or with collaborator objects, then this requires more complex testing.
It can get murky with this kind of testing. What is important is that one does not test the implementation (but sometimes that is crucial), only the interactions and requirements of the target “Unit” under test. What constitutes a “unit” is very important.
.
Example
The method being tested invokes a method on a collaborating object and that object throws an exception. In the catch block, the exception is logged using the logging utility collaborator . Though not part of an explicit API, that logging may be critical to the use of a system. For example, an enterprise log monitoring system expects this logging for support or security concerns. A simple class Shop is shown in figure 1,
Figure 1, the class to test
public class Shop { private ShoppingSvc svc; /** * Get product name. * @param id the unique product id * @return the product name */ public String getProduct(int id){ String name = ""; try { name = svc.getProductName(id); } catch (Exception e) { Logger.getAnonymousLogger() .log(Level.SEVERE, "{result:\"failure\",id:\""+ id + "\"}"); } return name; } }
JMockit supports two type of testing: behavior and state-based.
Using the state based approach we create a mock for the getProductName(String) method of the collaborating (or dependent) class, ShoppingSvc. With JMockit this is easily done as an inline MockUp object with the target method mocked to throw an exception.
Listing 2, mocking
new MockUp<ShoppingSvc>() { @Mock public String getProductName(int id) throws IOException{ throw new IOException("Forced exception for testing"); } };
JMockit’s behavior based support is then used to test the catch clause handling. As in other mocking frameworks, a record-replay-verify phases are used. Since the side effect of the exception handler here is the use of the logging dependency and we are not testing the logger, we ‘behaviorally’ mock the Logger class.
We can do this in the test method signature, @Mocked final Logger mockLogger. This mocks every method in the logger class. Then we set an expectation on the log method being used in the exception handler, then verify the method was actually invoked.
The full test class is shown in figure 3 below and the sample code is in a repo on GitHub:https://github.com/josefbetancourt/examples-jmockit-exceptions.
An alternative to using both state and behavior mocking is to just specify the exception throwing with the expectations. The article “Mocking exception using JMockit” shows how to do this. Of course, the JMockit Tutorial has all the details.
Listing 3, the full test class
@RunWith(JMockit.class) public class ShopTest{ /** * * @param mockLogger Logger object that will be behaviorally mocked. */ @Test public void shouldLogAtLevelSevere(@Mocked final Logger mockLogger) { /** * state-based mock of collaborator ShoppingSvc */ new MockUp<ShoppingSvc>() { @Mock public String getProductName(int id) throws IOException{ throw new IOException("Forced exception for testing"); } }; // the SUT final Shop shop = new Shop(); // what we expect to be invoked new Expectations() {{ mockLogger.log(Level.SEVERE,anyString); }}; shop.getProduct(123); // actual invocation // verify that we did invoke the expected method of collaborator new Verifications(){{ mockLogger.log(Level.SEVERE, anyString); // we logged at level SEVERE }}; } }
Alternatives?
Just write better code so that you don’t need unit tests? This is mentioned in Functional Tests over Unit Tests
Test using a scripting language like Groovy? See Short on Time? Switch to Groovy for Unit Testing
Software
- JUnit: 4.12
- JMockit: 1.18
- JDK: 1.8
- Eclipse: Mars
- Maven: 3
Links
- Project in GitHub: https://github.com/josefbetancourt/examples-jmockit-exceptions
- Exception testing
- Functional Tests over Unit Tests
- Short on Time? Switch to Groovy for Unit Testing
- Exception Handling as a System Wide Concern
- Expecting Exceptions JUnit Rule
- JMockit
- JMockit Tutorial
- Use JMockit to Unit test logging output
- Mocking exception using JMockit
- Java Exception Antipatterns
- Groovy
Clik here to view.
