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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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
- xUnit Paterns - http://xunitpatterns.com/
- Test Double - http://xunitpatterns.com/Test%20Double.html
- Builder Eclipse Plugin - http://code.google.com/p/fluent-builders-generator-eclipse-plugin/