TDD, Uncategorized

Behaviour Driven Development with Mockito

I’ve been using EasyMock for 3 years now, and have been quite happy with the way it has helped me to test drive development. Sometimes though, I’ve felt that my tests have become hard to read. Hard to read tests is often a test smell and a sign that the code is poorly factored so I should be careful blaming this all on EasyMock. Seriously, EasyMock has been a good friend and I wasn’t really looking for a replacement. But then I stumbled across the Mockito mocking framework

First thing that impressed me was actually the documentation, which was really easy to read gave a good overview of the framework.  Also, a lot of good people seemed really excited about it, always a good thing. So, I decided to have a go at it. I haven’t looked back since.

I’ve used it for around a month now and gained some experience with this great framework. I really appreciate how clean my tests look. Another good thing is that Mockito invites you into writing BDD style tests. In my opinion testing of behaviour makes the tests less brittle.

Below is an example of two test cases in a purchase system. The method under test is completePurchase, a finalization of the purchase where the purchased product is paid and delivered to the customer.


import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.BDDMockito.given; 
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

public class CompletePurchaseTest {

   private PurchaseService purchaseService;
   
   @Mock
   private PaymentConnector paymentConnector;
   
   @Mock 
   private PurchaseRepository purchaseRepository;
   
   @Mock
   private DeliveryRepository deliveryRepository;
   
   private Product existingProduct;
   private Purchase purchase;

   @Before
   public void doBeforeEachTest() throws Exception {
      MockitoAnnotations.initMocks(this);
      existingProduct = ProductMother.makeProduct("R51", "5000");
      purchase = PurchaseMother.createPurchaseInInitializedState();
      purchaseService = new PurchaseService(paymentConnector, purchaseRepository, deliveryRepository);
   }

   /**
   * This is the "happy case" scenario for the complete purchase. 
   * First, we check if the payment is ok. If so then we deliver the product to the customer
   */
   @Test
   public void shouldReturnValidPurchaseReceiptWhenDeliveryAndPaymentSucceeds(){
      
      given(payment.complete(purchase)).willReturn(any(PaymentTransaction.class));
      
      given(deliveryRepository.deliver(purchase)).willReturn(createOkDelivery());
      
      PurchaseReceipt purchaseReceipt = purchaseService.complete(purchase );
      
      assertTrue(purchaseReceipt.isValidPurchase());

      verify(paymentConnector).complete(any(OrderReceipt.class));
      verify(deliveryRepository).deliver(purchase);
   }

   private Delivery createOkDelivery() {
        return new Delivery(DeliveryStatus.OK);
    }

   @Test(expected=SystemException.class)
   public void shouldNotDeliverProductWhenPaymentFails() throws Exception{
      
      SystemException systemException = new SystemException("Payment solution down");
      
      given(payment.complete(purchase)).willThrow(systemException);
      
      purchaseService.complete(purchase );
      
      fail("Expected a system exception");
      
      verify(payment).completePurchase(purchase);
      
      verify(deliveryRepository,never()).deliver(any(Purchase.class));
   } 
}

In the setup we initialize some objects that we need in the test, a product and a purchase in an initialization state. We also inject the collaborators into the class under test (PurchaseService), using constructor injection.

The example shows two tests:

The first one is the case where we succesfully execute a payment and then deliver the product to the customer. On lines 41 and 43 we set up the mocks, line 45 executes the tests. On line 47 it is asserted that we have received a valid PurchaseReceipt. The two last lines verifies that the correct behaviour on the mock was executed.

The second tests is a failure situation. We instrument the mock to return a technical error from the payment solution, many situations this may happen, so we can’t deliver the product. Notice the last verify. Here we check that the deliver method of mock deliveryRepository is never called. The test expects a SystemException as a result.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s