Mock Objects: Shortcomings and Use Casesby Alex Ruiz AbstractWriting unit tests is not easy. Most of the time, we need to test code that uses complex collaborators such as databases, application servers, or software modules that have not been written yet. We also may need to deal with conditions that are difficult to generate in a test environment. Setting up these dependencies may require a considerable amount of time and energy, counteracting the benefits of having automated tests. This article looks at Mock Objects, a testing technique from the XP community that offers a way to test our code in isolation by simulating those external dependencies. As with any other tool, we need to be careful and avoid overusing them. Mock Objects OverviewIn recent years, developers have revived the benefits of writing their own tests, agreeing that finding and fixing defects in software is expensive. As a result, Unit Testing has become an essential part of the software development process, as a way to find coding mistakes and to help identify system requirements. The main goal of unit testing is to test in isolation a single unit of work, which is usually a class. Testing code in isolation is difficult, especially when dealing with dependencies that are not easy or quick to set up in our tests. The more difficult it is to write and maintain unit tests, the greater the chances that developers will feel discouraged and stop writing tests. Tim Mackinnon, Steve Freeman, and Philip Craig introduced the idea of mock objects in their paper "Endo-Testing: Unit Testing with Mock Objects," which they presented at XP2000. Mock objects, or mocks, simulate difficult-to-use and expensive-to-use collaborators and provide a way to:
Although useful, mocks are not a panacea and their overuse can bring more harm than good to our projects. Shortcomings of MocksLet's look at a few items that the mock programmer needs to be aware of. Mocks may hide integration problemsThis is especially true if we test code using mocks exclusively, without writing integration tests. Consider the example in Figure 1.
The
Proponents of mock objects suggest that we can save a significant amount of time and effort by mocking Mocks add clutter and duplication to test code
The following code listing uses EasyMock to test that
@Before public void setUp() {
mockEmployeeDAO = createMock(EmployeeDAO.class);
employeeBO = new EmployeeBO(mockEmployeeDAO);
employee = new Employee("Alex", "CA", "US");
}
@Test public void shouldAddNewEmployee() {
mockEmployeeDAO.insert(employee);
replay(mockEmployeeDAO);
employeeBO.addNewEmployee(employee);
verify(mockEmployeeDAO);
}
@Test public void shouldUpdateEmployee() {
mockEmployeeDAO.update(employee);
replay(mockEmployeeDAO);
employeeBO.updateEmployee(employee);
verify(mockEmployeeDAO);
}
The test method
Usage of mocks usually follows this pattern:
Such a pattern introduces duplication in test code, which is evident in the test methods
/**
* Understands a template for usage of EasyMock mocks.
* @author Alex Ruiz
*/
public abstract class EasyMockTemplate {
/** Mock objects managed by this template */
private final List<Object> mocks = new ArrayList<Object>();
/**
* Constructor.
* @param mocks the mock objects this template will manage.
* @throws IllegalArgumentException if the list of mock objects is
Now, let's refactor our test to use EasyMockTemplate:
@Test public void shouldAddNewEmployee() {
EasyMockTemplate t = new EasyMockTemplate(mockEmployeeDao) {
@Override protected void expectations() {
mockEmployeeDAO.insert(employee);
}
@Override protected void codeToTest() {
employeeBO.addNewEmployee(employee);
}
};
t.run();
}
@Test public void shouldUpdateEmployee() {
EasyMockTemplate t = new EasyMockTemplate(mockEmployeeDao) {
@Override protected void expectations() {
mockEmployeeDAO.update(employee);
}
@Override protected void codeToTest() {
employeeBO.updateEmployee(employee);
}
};
t.run();
}
EasyMockTemplate brings the following benefits:
You can download the latest version of Tests using mocks may be fragileMock testing is glass box testing, which requires intimate knowledge of class internals. This is a side effect of the interaction-based nature of mocks. Justifiable changes in the implementation of a method may break tests using mocks, even if the result of executing such a method is still the same.
In our example,
Conversely, black box testing (for example, functional testing) is less likely to break if the internals of the system change while the outcome stays the same, because the testing is based solely on the knowledge of system requirements.
In our example, integration tests that verify the correctness of the data being stored in the database (instead of verifying how data was stored) will not fail after replacing
|
Article Tools Related Products Check out the products mentioned in this article:Related Technologies Related Articles Bookmark Article
|