Mittwoch, 23. November 2011

Test Doubles and Mocking Patterns

Yesterday I refactored a unit test with over 2000 lines of code, now the test has around 900 lines of code. One pattern I found in the test was that for every kind of test double a mock object was used.

I prefer the following test design rule for mock objects and test doubles. Object which contains logic e.g. of type service, factory or repository should be mocked instead of DTOs, value objects or entities (plain Java Beans) with getters and setters e.g. configuration objects this kind of object should never be mocked.

Here small example, a test with to much mocking, please don't write such kind of tests:
@RunWith(JMock.class)
public class SaleServiceImplTest {
Mockery context = new JUnit4Mockery(){{
setImposteriser(ClassImposteriser.INSTANCE);
}};
SaleServiceImpl saleService;
SellerDao mockSellerDao;
PositionDao mockPositionDao;
@Before
public void setUp() throws Exception {
saleService = new SaleServiceImpl();
mockPositionDao = context.mock(PositionDao.class);
saleService.setPositionDao(mockPositionDao);
mockSellerDao = context.mock(SellerDao.class);
saleService.setSellerDao(mockSellerDao);
}
@Test
public void testPurchase() {
final Sale mockSale = context.mock(Sale.class);
final Position mockPosition = context.mock(Position.class);
final Seller mockSeller = context.mock(Seller.class);
final PositionKey mockPositionKey = context.mock(PositionKey.class);
context.checking(new Expectations(){{
allowing(mockSale).getPositions();
will(returnValue(Arrays.asList(new Position[]{mockPosition})));
allowing(mockPosition).getSeller();
will(returnValue(mockSeller));
allowing(mockSeller).getBasarNumber();
will(returnValue(23L));
allowing(mockSellerDao).getSeller(23L);
will(returnValue(mockSeller));
allowing(mockPosition).getPositionKey();
will(returnValue(null));
allowing(mockPositionDao).createPositionKey();
will(returnValue(mockPositionKey));
allowing(mockPosition).setPositionKey(mockPositionKey);
allowing(mockPositionDao).insertPosition(mockPosition);
}});
saleService.purchase(mockSale);
}
}


This simple unit test above has to much mocking logic and should be refactored to this test:
@RunWith(JMock.class)
public class SaleServiceImplTest {
Mockery context = new JUnit4Mockery();
SaleServiceImpl saleService;
SellerDao mockSellerDao;
PositionDao mockPositionDao;
Seller seller23 = new Seller();
Position positionOne = new Position();
PositionKey positionOneKey = new PositionKey();
@Before
public void setUp() throws Exception {
saleService = new SaleServiceImpl();
mockPositionDao = context.mock(PositionDao.class);
saleService.setPositionDao(mockPositionDao);
mockSellerDao = context.mock(SellerDao.class);
saleService.setSellerDao(mockSellerDao);
}
@Test
public void testPurchase() {
seller23.setBasarNumber(23L);
positionOne.setSeller(seller23);
context.checking(new Expectations(){{
allowing(mockSellerDao).getSeller(seller23.getBasarNumber());
will(returnValue(seller23));
allowing(mockPositionDao).createPositionKey();
will(returnValue(positionOneKey));
allowing(mockPositionDao).insertPosition(positionOne);
}});
Sale sale = new Sale();
sale.addPosition(positionOne);
saleService.purchase(sale);
assertThat(positionOne.getPositionKey(), is(positionOneKey));
}
}


If you like you could use the builder pattern for the simple objects like sale to get the test a little bit nicer (see the links for eclipse tooling). A smell is when you need to mock classes, in most cases then you should use another test double pattern for this kind of objects, or something with the design of the SUT is wrong.

What I like to say is you should not always use our preferred mocking framework to create a mock object as test double for everything. There are situations where mock object I believe are a test design anti pattern / smell. Think about stubs and dummy object before creating a mock object and read the great xUnit test pattern book “xUnit Test Patterns” from Gerard Meszaros. Then you are on the right way to get an agile tester ;-)

Links