Simulating exceptions

The developer realizes that SAP’s send method may throw a SapException if a problem occurs. This leads to a new requirement:

The system should return the list of invoices that failed to be sent to SAP. A failure should not make the program stop. Instead, the program should try to send all the invoices, even though some of them may fail.

One easy way to implement this is to try to catch any possible exceptions. If an exception happens, we store the failed invoice as shown in the following listing.

Listing 6.14 Catching a possible SAPException

public List<Invoice> sendLowValuedInvoices() {
  List<Invoice> failedInvoices = new ArrayList<>();
 
  List<Invoice> lowValuedInvoices = filter.lowValueInvoices();
  for(Invoice invoice : lowValuedInvoices) {
    String customer = invoice.getCustomer();
    int value = invoice.getValue();
    String sapId = generateId(invoice);
 
    SapInvoice sapInvoice = new SapInvoice(customer, value, sapId);
 
    try {                          ❶
      sap.send(sapInvoice);
    } catch(SAPException e) {
      failedInvoices.add(invoice);
    }
  }
 
  return failedInvoices;           ❷
}

❶ Catches the possible SAPException. If that happens, we store the failed invoice in a list.

❷ Returns the list of failed invoices

How do we test this? By now, you probably see that all we need to do is to force our sap mock to throw an exception for one of the invoices. We should use Mockito’s doThrow() .when() chain of calls. This is similar to the when() API you already know, but now we want it to throw an exception (see listing 6.15). Note that we configure the mock to throw an exception for the frank invoice. Then we assert that the list of failed invoices returned by the new sendLowValuedInvoices contains that invoice and that SAP was called for both the mauricio and the frank invoices. Also, because the SAP interface receives a SapInvoice and not an Invoice, we must instantiate three invoices (Maurício’s, Frank’s, and Steve’s) before asserting that the send method was called.

Listing 6.15 Mocks that throw exceptions

@Test
void returnFailedInvoices() {
  Invoice mauricio = new Invoice("Mauricio", 20);
  Invoice frank = new Invoice("Frank", 25);
  Invoice steve = new Invoice("Steve", 48);
  List<Invoice> invoices = Arrays.asList(mauricio, frank, steve);
  when(filter.lowValueInvoices()).thenReturn(invoices);
 
  String date = LocalDate.now()
    .format(DateTimeFormatter.ofPattern("MMddyyyy"));
  SapInvoice franksInvoice = new SapInvoice("Frank", 25, date + "Fr");
  doThrow(new SAPException())
    .when(sap).send(franksInvoice);                                ❶
 
 
  List<Invoice> failedInvoices = sender.sendLowValuedInvoices();   ❷
  assertThat(failedInvoices).containsExactly(frank);               ❷
 
  SapInvoice mauriciosInvoice =
    new SapInvoice("Mauricio", 20, date + "Ma");
  verify(sap).send(mauriciosInvoice);                              ❸
 
  SapInvoice stevesInvoice =
    new SapInvoice("Steve", 48, date + "St");
  verify(sap).send(stevesInvoice);                                 ❸
}

❶ Configures the mock to throw an exception when it receives Frank’s invoice. Note the call to doThrow().when(): this is the first time we use it.

❷ Gets the returned list of failed invoices and ensures that it only has Frank’s invoice

❸ Asserts that we tried to send both Maurício’s and Steve’s invoices

NOTE Creating SapInvoices is becoming a pain, given that we always need to get the current date, put it in the MMddyyyy format, and concatenate it with the first two letters of the customer’s name. You may want to extract all this logic to a helper method or a helper class. Helper methods are widespread in test code. Remember, test code is as important as production code. All the best practices you follow when implementing your production code should be applied to your test code, too. We will discuss test code quality.

Configuring mocks to throw exceptions enables us to test how our systems would behave in unexpected scenarios. This is perfect for many software systems that interact with external systems, which may not behave as expected. Think of a web service that is not available for a second: would your application behave correctly if this happened? How would you test the program behavior without using mocks or stubs? How would you force the web service API to throw you an exception? Doing so would be harder than telling the mock to throw an exception.

The requirement says one more thing: “A failure should not make the program stop; rather, the program should try to send all the invoices, even though some of them may fail.” We also tested that in our test method. We ensured that steve’s invoice—the one after frank’s invoice, which throws the exception—is sent to SAP.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *