Verify the Exception Payload

We can generalize the idea of verifying the exception message. In the last section, we made a deliberate decision to temporarily ignore the cause of the exception, the other of the two attributes that could help us distinguish why the exception was thrown. We could have used the cause as an alternative means of verification or even to supplement our verification. In fact, the message and cause are the only user-settable attributes2 of the Throwable class, the base class for exceptions of all types3 in Java. Throwable closely resembles the base class of exception hierarchies in other languages and frameworks. For example, C# has the System.Exception class that has InnerException and Message attributes. C++ has only the what() accessor as equivalent to Java’s message—but only definable by the class itself—and no cause.

2. There are attributes that are only set by the system, such as the stack trace in Java and a more thorough description of the context in C#.

3. Java distinguishes between Errors, RuntimeExceptions, and other exceptions at a high level. An Error is a critical and unrecoverable system issue. A RuntimeException does not need to be declared and is considered a possibly unpredictable or unhandleable event. All other exceptions must be declared.

Regardless of the language, exceptions are extensible. They can have attributes beyond those defined by the base system classes. While those attributes may not have an interface known to all, code that knows about the specific exceptions can reasonably know about the attributes. We will talk about reasons for and usages of extended exceptions later in the chapter. For now, let’s simply assume they exist for a reason possibly beyond testability and examine them for testing.

In the test in Listing 11-5, we can verify the cause, or at least the type of the cause, simply by adding

assertTrue(exp.getCause() instanceof ConnectException);

inside the catch block. We have now verified that the cause of the exception is of the type we expect. This has the same shortcomings by itself as verifying by type, as discussed earlier in this chapter. However, layering this on top of the verification of the primary exception’s type and message verifies the primary exception more strongly. We can go further by applying all of our techniques for exception verification to the causal exception as well. In fact, we could drill down the chain, but we should keep in mind that the deeper we verify, the more we may potentially couple our test to implementation details inappropriately.

Java defines the cause attribute of an exception. What about attributes of our own invention? Imagine an API in an embedded controller for smart ovens with elaborate touch screen interfaces and user-friendly interactive displays. One feature of such an oven might allow the user to enter the desired temperature through a keypad rather than a dial. A dial has the concept of a temperature limit built into its markings and range of travel, whereas a digital display may only have similar inherent limits based on the number of digits in the display. Consider the subset of the oven’s controller software in Listing 11-7.

Listing 11-7: A subset of a hypothetical oven-controller API for a smart oven

public class OvenController {
  private final int ratedTemperature;
  public OvenController(int ratedTemperature) {
    this.ratedTemperature = ratedTemperature;
  }

  public void setPreHeatTemperature(int temperature)
      throws IllegalArgumentException {
    if (temperature > ratedTemperature) {
      throw new RequestedTemperatureTooHighException(
          ratedTemperature, temperature);
    }
    // Set the preheat temperature
  }
}

We infer from this code that the RequestedTemperatureTooHighException derives from IllegalArgumentException because we declare the latter4 but throw the former in the setPreHeatTemperature() method. But why did we supply the temperatures as arguments? Listing 11-8 shows a partial implementation of the exception.

4. Technically, we do not have to declare IllegalArgumentException because it is a Java runtime exception. However, you can declare runtime exceptions, and in this case it simplifies our example. A more realistic example would use an application-defined exception as the base class.

Listing 11-8: Partial implementation of the RequestedTemperatureTooHighException

public class RequestedTemperatureTooHighException
  extends IllegalArgumentException {
  private final int ratedTemperature;
  private final int requestedTemperature;

  public RequestedTemperatureTooHighException(
      int ratedTemperature, int requestedTemperature) {
    this.ratedTemperature = ratedTemperature;
    this.requestedTemperature = requestedTemperature;
  }

  public int getRatedTemperature() {
    return ratedTemperature;
  }

  public int getRequestedTemperature() {
    return requestedTemperature;
  }
  ...
}

The exception now carries detailed information about the reason it was thrown, information highly relevant to code that would catch this particular type of exception. This information also gives us a lever with which to more thoroughly verify the behavior of the software throwing the exception. Listing 11-9 contains such a test for our OvenController.

Listing 11-9: A more rigorous test for the OvenController from Listing 11-7

@Test
public void testSetPreHeatTemperature() {
  int ratedTemperature = 600;
  int requestedTemperature = 750;
  OvenController sut = new OvenController(ratedTemperature);

  try {
    sut.setPreHeatTemperature(requestedTemperature);
    fail();
  } catch(RequestedTemperatureTooHighException exp) {
    assertEquals(exp.getRatedTemperature(), ratedTemperature);
    assertEquals(exp.getRequestedTemperature(),
        requestedTemperature);
  }
}

Tying the input values to the error—as communicated via the exception—gives us a much higher coupling of inputs to outputs in our test and thus a higher level of confidence that our test verifies the software and exercises the specific exception path that we targeted. Exception payloads can consist of any data type, allowing rich verification opportunities. We will also see at the end of the chapter one of the ways that this testability improvement can also improve other aspects of our software.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.142.2