Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Why Unit Testing Matters

DZone's Guide to

Why Unit Testing Matters

Going over unit testing with JUnit and Mockito, and why it is a life saver for your project.

· DevOps Zone
Free Resource

Download “The DevOps Journey - From Waterfall to Continuous Delivery” to learn learn about the importance of integrating automated testing into the DevOps workflow, brought to you in partnership with Sauce Labs.

I've had the opportunity to look at unit testing from three different perspectives — Developer, Architect, and Director. My opinion has swung from thinking of unit testing as being a necessary evil to a project saver. 

After managing a product with more than half a million lines of code, I was tasked with an eCommerce implementation. I started with the aim of achieving a 100% unit test coverage goal.Maintaining the quality of code is critical for large projects across distributed teams. Static code analysis and unit testing are a part of standard toolkit in the software developmenet pactices.

The ecommerce poject is being implemented using ATG. ATG has been around for more than 15 years and has evolved over the years into a mature JEE solution. The ATG product development started before TDD was in vogue and has not added much support since for TDD. There have been a couple of attempts to provide the tools needed to test your ATG code with the Nucleus components. A couple of notable mentions being DUST  and DynaCactus. Both these tools provided an ability to inject Nucleus components into your code. They also required that the Application Server be up for them to utilize the Nucleus component. 

The focus of this article will be on using JUnit with Mockito. Mockito is a flexible mocking library that allows rapid test development. With annotations, writing a Mockito unit test is a breeze. The tests run fast because they don't initialize the entire class dependency 

What Should I Test?

  1. State Verification — JUnit comes with a handy set of assertions to verify the state of the object after the execution of the program. A traditional JUnit test relying only on State verification does not ensure that the program has gone through the execution path designed by the programmer.

  2. Behavior Verification — Sometimes a "system under test” may not be stateful or the system may not change the state after execution. In these cases, a test verifying the behavior or interaction is appropriate. Behavior verification focusses on ensuring that the program follows the execution path determined by the programmer. Mocking frameworks help us verify behavior and interactions of the SUT.

In this article, we will see examples of both state and behavior verification. Behavior Driven Development (BDD) is an extension of Test Driven Development and helps you identify tests that verify the behavior desired by the business requirements. BDD will be a topic for another discussion and will not be covered in this article.

The Use Case

We will use a simple use case to demostrate the use of Mockito with ATG. The product owner would like to place a cap on the maxium quantity that a person can order for any given item. The implementation calls for the CommerceItemManager to be extended with a method — addItemQuantityToShippingGroup.

public void addItemQuantityToShippingGroup(Order pOrder, String pCommerceItemId, String pShippingGroupId, long pQuantity) throws CommerceException {

  long quantity = pQuantity;
  ShippingGroupCommerceItemRelationship rel = null;
  CommerceItem item = pOrder.getCommerceItem(pCommerceItemId);
  boolean exists = true;
  try {
    rel = getShippingGroupManager().getShippingGroupCommerceItemRelationship(pOrder, pCommerceItemId, pShippingGroupId);
  } catch (RelationshipNotFoundException e) {
      exists = false;
  }
  long unassignedQty = getUnassignedQuantityForCommerceItem(item);

  if (quantity > unassignedQty) {
    quantity = unassignedQty;
  }

  if (unassignedQty == LONG_INIT_VAL) {
    unassignedQty = getShippingGroupManager().getRemainingQuantityForShippingGroup(item);
  }
  if (quantity > unassignedQty) {
    throw new InvalidParameterException(ResourceUtils.getMsgResource("QuantityTooBig", ORDER_RESR, sResourceBundle));
  }
  if (exists) {
    if (rel.getRelationshipType() != 100) {
      throw new InvalidTypeException(ResourceUtils.getMsgResource("IncompatibleRelationshipType", ORDER_RESR, sResourceBundle));
    }
    rel.setQuantity(rel.getQuantity() + quantity);
  } else {
     rel = (ShippingGroupCommerceItemRelationship) getOrderTools().createRelationship("shippingGroupCommerceItem");
     rel.setRelationshipType(100);
     getOrderTools().initializeRelationship(pOrder, rel, pShippingGroupId, pCommerceItemId);
     rel.setQuantity(quantity);
  }

}

The implementation has multiple conditionals in one method and a few internal method calls. We can debate about how well the method is structured, but we started with the premise that we are writing unit tests for a legacy implementation.

A good test covers every path that your method is going to traverse in execution. You should create one or more unit test for each conditional in the method. 

Let's start by setting up the test case and mocking the objects that are used in the implementation. 

public class CustomCommerceItemManagerTest {

@Spy
CustomCommerceItemManager commerceItemManager;

@Mock
Order order;

@Mock
CustomOrderTools orderTools;

@Mock
CustomCommerceItemImpl commerceItemInstance;
@Mock
CustomPriceListManager pricelistManager;
@Mock
ShippingGroupManager shipGroupManager;
@Mock
ShippingGroupCommerceItemRelationship relationship;

//Set up the test
String commerceItemId = "ci456";
String shippingGroupId = "sg123";

@Before
public void setUp() throws Exception {
initMocks(this);

commerceItemManager.setOrderTools(orderTools);

commerceItemManager.setPriceListManager(pricelistManager);

commerceItemManager.setShippingGroupManager(shipGroupManager);

}

}

We are using @Mock annotation to mock the dependent classes. Line 3 uses @Spy annotation for the CustomCommerceItemManager instance. 

Mockito Spy, as the name suggests, is used to spy on a real object. Spy comes in handy when we have objects being managed by an IoC container. We will be using Mockito spies to verify object behavior, override object behavior and make your objects do nothing if we so choose. Read more on Mockito Spy here.

Test Case 1 

The method tries looking up a ShippingGroupCommerceItemRelationship (rel). If it finds an existing relationship it uses it. Otherwise it creates a new relationship.

  ShippingGroupCommerceItemRelationship rel = null;
  boolean exists = true;
  try {
    rel = getShippingGroupManager().getShippingGroupCommerceItemRelationship(pOrder, pCommerceItemId, pShippingGroupId);
  } catch (RelationshipNotFoundException e) {
      exists = false;
  }
...
...
...
if (exists) {
    if (rel.getRelationshipType() != 100) {
      throw new InvalidTypeException(ResourceUtils.getMsgResource("IncompatibleRelationshipType", ORDER_RESR, sResourceBundle));
    }
    rel.setQuantity(rel.getQuantity() + quantity);
  } else {
     rel = (ShippingGroupCommerceItemRelationship) getOrderTools().createRelationship("shippingGroupCommerceItem");
}

While writing test cases, a good practice is to break the test into three parts - Setting up the behavior, invoking the method under test, and verifying the behavior. A JUnit test case is also code, strcuture it well and make it as beautiful as your implementation. Bonus points if you can add comments ;). Let's look at our test case:

@SuppressWarnings("unchecked")
@Test
public void shouldCreateRelationshipAndAddItem() throws CommerceException {
// Mock the behavior
Mockito.when(order.getCommerceItem(Mockito.anyString())).thenReturn(
commerceItemInstance);

Mockito.when(
commerceItemManager.getShippingGroupManager()
.getShippingGroupCommerceItemRelationship(order,
commerceItemId, shippingGroupId)).thenThrow(
RelationshipNotFoundException.class);
Mockito.when(orderTools.createRelationship("shippingGroupCommerceItem"))
.thenReturn(relationship);

// Invoke the Method under test
commerceItemManager.addItemQuantityToShippingGroup(order, commerceItemId,
shippingGroupId, 2L);


//verify behavior
Mockito.verify(commerceItemManager.getOrderTools())
.createRelationship(Mockito.anyString());

Mockito.verify(commerceItemManager, Mockito.times(2))
.addItemQuantityToShippingGroup(order, commerceItemId, shippingGroupId,
2L);

//Verify State
assertEquals(relationship.getQuantity(),2L);

}

Test Case 2: Testing Exceptional Scenarios

JUnit4 supports handling and expecting exceptions during execution of a method. 

@Test(expected=InvalidTypeException.class)
public void shouldThrowInvalidTypeExceptionForIncompatibleRelationshipType() throws CommerceException {
// Mock the behavior
Mockito.when(order.getCommerceItem(Mockito.anyString())).thenReturn(
commerceItemInstance);

Mockito.when(
commerceItemManager.getShippingGroupManager()
.getShippingGroupCommerceItemRelationship(order,
commerceItemId,shippingGroupId )).thenReturn(
relationship);
Mockito.when(relationship.getRelationshipType()).thenReturn(101);
Mockito.when(relationship.getQuantity()).thenReturn(1L);
Mockito.when(commerceItemInstance.getQuantity()).thenReturn(2L);

// Invoke the Method under test
commerceItemManager.addItemQuantityToShippingGroup(order, commerceItemId,
shippingGroupId, 2L);

/* The test is expected to throw an exception of Type InvalidTypeException
and is annotated to pass the test when it encounters that exception.
*/
}

Summary

We've demonstrated the use of JUnit+Mockito to test an ATG application. Writing unit tests adds stability and makes your code mantianable. A well written Unit test also serves as documentation of your code. Structure your unit tests as well as you would the implementation. A good practice is to write multiple test cases for each method under test, covering each path that the method will take. As you increase the unit test coverage of your application, you will observe that the code is more modular and readable. 

Resources:

http://martinfowler.com/articles/mocksArentStubs.html

Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure, brought to you in partnership with Sauce Labs

Topics:
unit testing ,ecommerce ,java 7 ,mockito ,junit

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}