Unit Testing Using Mocks – Testing Techniques 5
… and testing the AddressService class:
@Component public class AddressService { private static final Logger logger = LoggerFactory.getLogger(AddressService.class); private AddressDao addressDao; /** * Given an id, retrieve an address. Apply phony business rules. * * @param id * The id of the address object. */ public Address findAddress(int id) { logger.info("In Address Service with id: " + id); Address address = addressDao.findAddress(id); address = businessMethod(address); logger.info("Leaving Address Service with id: " + id); return address; } private Address businessMethod(Address address) { logger.info("in business method"); // Apply the Special Case Pattern (See MartinFowler.com) if (isNull(address)) { address = Address.INVALID_ADDRESS; } // Do some jiggery-pokery here.... return address; } private boolean isNull(Object obj) { return obj == null; } @Autowired @Qualifier("addressDao") void setAddressDao(AddressDao addressDao) { this.addressDao = addressDao; } }
…by replacing he data access object with a mock object.
Before continuing, it would be a good idea to define what exactly a mock object is and how it differs from a stub. If you read my last blog, you’ll remember that I let Martin Fowler define a stub object as:
“Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.”
…which is taken from his essay Mocks Aren’t Stubs.
So, how do mock object differ to stubs? When you hear people talk about mock objects, they often mention that they’re mocking behaviour or mocking roles, but what does that mean? The answer lies in the way a unit test and a mock object work together to test your object. The mock object scenario goes like this:
- A mock object is defined in the test.
- The mock object is injected into your object under test
- The test specifies which methods on the mock object will be called, plus the arguments and return values. This is known as ‘setting expectations’.
- The test then runs.
- The test then asks the mock to verify that all the method calls specified in step three were called correctly. If they were then the test passes. If they weren’t then the test fails.
Therefore, mocking behaviour or mocking roles really means checking that your object under test calls methods on a mock object correctly and failing the test if it doesn’t; hence, you’re asserting on the correctness of method calls and the execution path through your code, rather than, in the case of a regular unit test, the return value of the method under test.
Although there are several professional mocking frameworks available, for this example I first decided to produce my own AddressDao mock, which fulfils the above requirements. After all, how hard can it be?
public class HomeMadeMockDao implements AddressDao { /** The return value for the findAddress method */ private Address expectedReturn; /** The expected arg value for the findAddress method */ private int expectedId; /** The actual arg value passed in when the test runs */ private int actualId; /** used to verify that the findAddress method has been called */ private boolean called; /** * Set and expectation: the return value for the findAddress method */ public void setExpectationReturnValue(Address expectedReturn) { this.expectedReturn = expectedReturn; } public void setExpectationInputArg(int expectedId) { this.expectedId = expectedId; } /** * Verify that the expectations have been met */ public void verify() { assertTrue(called); assertEquals("Invalid arg. Expected: " + expectedId + " actual: " + expectedId, expectedId, actualId); } /** * The mock method - this is what we're mocking. * * @see com.captaindebug.address.AddressDao#findAddress(int) */ @Override public Address findAddress(int id) { called = true; actualId = id; return expectedReturn; } }
The unit test code that supports this mock is:
public class MockingAddressServiceWithHomeMadeMockTest { /** The object to test */ private AddressService instance; /** * We've written a mock,,, */ private HomeMadeMockDao mockDao; @Before public void setUp() throws Exception { /* Create the object to test and the mock */ instance = new AddressService(); mockDao = new HomeMadeMockDao(); /* Inject the mock dependency */ instance.setAddressDao(mockDao); } /** * Test method for * {@link com.captaindebug.address.AddressService#findAddress(int)}. */ @Test public void testFindAddressWithEasyMock() { /* Setup the test data - stuff that's specific to this test */ final int id = 1; Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country"); /* Set the Mock Expectations */ mockDao.setExpectationInputArg(id); mockDao.setExpectationReturnValue(expectedAddress); /* Run the test */ instance.findAddress(id); /* Verify that the mock's expectations were met */ mockDao.verify(); } }
Okay, although this demonstrates the steps required to carry out a unit test using a mock object, it’s fairly rough and ready, and very specific to the AddressDao/AddressService scenario. To prove that it’s already been done better, the following example uses easyMock as a mocking framework. The unit test code in this more professional case is:
@RunWith(UnitilsJUnit4TestClassRunner.class) public class MockingAddressServiceWithEasyMockTest { /** The object to test */ private AddressService instance; /** * EasyMock creates the mock object */ @Mock private AddressDao mockDao; /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { /* Create the object to test */ instance = new AddressService(); } /** * Test method for * {@link com.captaindebug.address.AddressService#findAddress(int)}. */ @Test public void testFindAddressWithEasyMock() { /* Inject the mock dependency */ instance.setAddressDao(mockDao); /* Setup the test data - stuff that's specific to this test */ final int id = 1; Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country"); /* Set the expectations */ expect(mockDao.findAddress(id)).andReturn(expectedAddress); replay(); /* Run the test */ instance.findAddress(id); /* Verify that the mock's expectations were met */ verify(); } }
…which i hope you’ll agree is more progressional than my quick attempt at writing a mock.
The main criticism levelled at using mock objects is that they closely couple the unit test code to the implementation of the production code. This is because the code that sets the expectations closely tracks the execution path of the production code. This means that subsequent refactoring of the production code can break a multitude of tests even though the class still fulfills its interface contract. This give rise to the assertion that mock tests are fairly brittle and that you’ll spend time fixing them unnecessarily, which from experience I agree with – although using ‘non-strict’ mocks, which don’t care about the order in which methods expectations are called, alleviates the problem to a degree.
On the other hand, once you know how to use a framework like easyMock, producing unit tests that isolate you object under test can be done very quickly and efficiently.
In self critiquing this example code, I’d like to point out that I think that using a mock object is overkill in this scenario, plus, you could also easily argue that I’m using a mock as a stub.
Several years ago, when I first came across easyMock, I used mocks everywhere, but recently I’ve come to prefer manually writing stubs for application boundary classes, such as DAOs, and objects that merely return data. This is because stub based tests are arguably a lot less brittle than mock based tests especially when all you need to is access data.
Why use mocks? Mocks good at testing an application written using the ‘tell don’t ask’ technique, to verify that a method with a void return is called.
Reference: Unit Testing Using Mocks – Testing Techniques 5 from our JCG partner at the Captain Debug blog
Related Articles :
- Testing Techniques – Not Writing Tests
- The Misuse of End To End Tests – Testing Techniques 2
- What Should you Unit Test? – Testing Techniques 3
- Regular Unit Tests and Stubs – Testing Techniques 4
- Creating Stubs for Legacy Code – Testing Techniques 6
- More on Creating Stubs for Legacy Code – Testing Techniques 7
- Why You Should Write Unit Tests – Testing Techniques 8
- Some Definitions – Testing Techniques 9
- Using FindBugs to produce substantially less buggy code
- Developing and Testing in the Cloud