Java

Recent site activity

EasyMock examples

EasyMock is a mock object framework for Java.  You can find it here.  The following example shows how to mock up the dependencies for your system under test or SUT.  In this particular example, I'm testing a PricingServiceImpl class and it has a dependency to a DataAccess type.  Using interface-based programming makes it easy to swap in different implementations of an interface type.  I'm a firm believer in dependency injection (DI).  Using DI makes mocking even easier when driving out a design with unit tests.  First we have the interface types for PricingService and DataAccess:

 1 package examples.easymock;
2
3
import java.math.BigDecimal;
4
5
/**
6 * @author Christopher Bartling, Pintail Consulting LLC
7 * @since Aug 29, 2008 11:59:54 PM
8 */
9 public interface PricingService {
10 void setDataAccess(DataAccess dataAccess);
11
12
BigDecimal getPrice(String sku) throws SkuNotFoundException;
13 }

 1 package examples.easymock;
2
3
import java.math.BigDecimal;
4
5
/**
6 * @author Christopher Bartling, Pintail Consulting LLC
7 * @since Aug 30, 2008 12:01:42 AM
8 */
9 public interface DataAccess {
10 BigDecimal getPriceBySku(String sku);
11 }


Next up is the PricingServiceImpl class, which is the system under test or SUT for the subsequent unit test.  Note that the PricingServiceImpl collaborates with a DataAccess type, but it does not take on the responsibility of creating the DataAccess implementation.  That responsibility lies elsewhere in the application.  In this scenarion, the unit test will provide the implementation (a mock implementation using EasyMock) and inject that instance into the PricingServiceImpl instance.

 1 package examples.easymock;
2
3
import java.math.BigDecimal;
4
5
/**
6 * @author Christopher Bartling, Pintail Consulting LLC
7 * @since Aug 30, 2008 12:00:12 AM
8 */
9 public class PricingServiceImpl implements PricingService {
10 private DataAccess dataAccess;
11
12
public void setDataAccess(DataAccess dataAccess) {
13 this.dataAccess = dataAccess;
14 }
15
16
public BigDecimal getPrice(String sku) throws SkuNotFoundException {
17 BigDecimal price = this.dataAccess.getPriceBySku(sku);
18 if (price == null) {
19 throw new SkuNotFoundException("SKU not found.");
20 }
21 return price;
22 }
23 }
24


Finally the unit test.  EasyMock is used to create mock instances of the DataAccess type, suitable for testing the PricingService implementation.  

 1 package examples.easymock;
2
3
import static org.easymock.EasyMock.*;
4 import static org.junit.Assert.assertNotNull;
5 import org.junit.Before;
6 import org.junit.Test;
7
8
import java.math.BigDecimal;
9
10
/**
11 * @author Christopher Bartling, Pintail Consulting LLC
12 * @since Aug 29, 2008 11:58:45 PM
13 */
14 public class PricingServiceTests {
15
16
private static final String SKU = "3283947";
17 private static final String BAD_SKU = "-9999993434";
18
19
private PricingService systemUnderTest;
20 private DataAccess mockedDependency;
21
22
@Before
23 public void doBeforeEachTestCase() {
24 systemUnderTest = new PricingServiceImpl();
25 mockedDependency = createMock(DataAccess.class);
26 systemUnderTest.setDataAccess(mockedDependency);
27 }
28
29
@Test
30 public void getPrice() throws SkuNotFoundException {
31
32
33
// Set expectations on mocks.
34 expect(mockedDependency.getPriceBySku(SKU)).andReturn(new BigDecimal(100));
35
36
// Set mocks into testing mode.
37 replay(mockedDependency);
38
39
final BigDecimal price = systemUnderTest.getPrice(SKU);
40 assertNotNull(price);
41
42
// Verify behavior.
43 verify(mockedDependency);
44 }
45
46
@Test(expected = SkuNotFoundException.class)
47 public void getPriceNonExistentSkuThrowsException() throws SkuNotFoundException {
48
49
50
// Set expectations on mocks.
51 expect(mockedDependency.getPriceBySku(BAD_SKU)).andReturn(null);
52
53
// Set mocks into testing mode.
54 replay(mockedDependency);
55
56
final BigDecimal price = systemUnderTest.getPrice(BAD_SKU);
57 }
58
59
@Test(expected = RuntimeException.class)
60 public void getPriceDataAccessThrowsRuntimeException() throws SkuNotFoundException {
61
62
63
// Set expectations on mocks.
64 expect(mockedDependency.getPriceBySku(SKU)).andThrow(new RuntimeException("Fatal data access exception."));
65
66
// Set mocks into testing mode.
67 replay(mockedDependency);
68
69
final BigDecimal price = systemUnderTest.getPrice(SKU);
70 }
71 }

The setup of each test case is done in lines 23-27.  The PricingService instance is created, a DataAccess mock implementation is factoried by EasyMock and the dependency between the PricingService and the DataAccess instance is fulfilled by injecting the mock DataAccess instance into the PricingService implementation.  The SUT is ready for use.

The first test case is a happy path test.  We build up the expectation for the mocked dependency in line 34.  We expect that the SUT (PricingServiceImpl) will call DataAccess.getPriceBySku(String sku), and for this test, we configure the mock to return a BigDecimal object, representing the price.  Line 37 sets the mock into testing mode.  The test is executed in line 39, invoking the PricingService.getPrice(String sku) method on the PricingService implementation.  State and behavior verification occurs in lines 40 and 43.

The second test case verifies that when the call to DataAccess returns null that our PricingService implementation will throw a SkuNotFoundException.  Everything remains same except that the expectations of the DataAccess mock object changes (Line 51).  It will return null when getPriceBySku(String sku) is invoked.  Our service will be designed to test for null and throw a SkuNotFoundException when this situation occurs.

The third test case verifies that our PricingServiceImpl will allow RuntimeExceptions to bubble out of it.  RuntimeExceptions could be caused by a connection error to the database in DataAccess, for example.  In any case, a RuntimeException signals a fatal error has occurred and that fail error should bubble up through the application and be handled appropriately at a higher level.  PricingService will not take on the responsibility of handling RuntimeExceptions.  Again, everything remains same except that the expectations of the DataAccess mock object changes (Line 64).  It will throw a RuntimeException when getPriceBySku(String sku) is invoked.  Our service will be designed to let this RuntimeException bubble out when this situation occurs.