Friday, November 9, 2012

Testing with Mock Objects

What is a Mock Object?

A Mock Object is used in the testing of software to simulate components that are difficult to integrate with or are outside the scope of the software-under-test (more commonly a class-under-test).  Testing software is important for so many reasons, and Mock Objects make it easier to do so.

Why would someone want to use Mocks?

A commonly cited reason for not writing tests is that they are too difficult to set up.  Suppose you're working on an application that interacts with a relational database with a complicated data model.  Running a database and configuring it with seed data can be costly, time consuming, and (most importantly) tedious.  The root of the problem is that we're not trying to test the database; we're likely trying to test the business logic that operates on data in the database.  Mock objects allow your test to focus on the class-under-test, and the test becomes easier to maintain because the data it uses can be tweaked with the mocks in your test class (not in a database seed SQL script).

What are the available Mocking frameworks?

One of the most popular Mock Object frameworks for Java is EasyMock.  I've used EasyMock for a couple years with great results.  Here are some other Mock Object frameworks:


Here are some other useful resources related to Mock Objects:


How have I used Mocks?

As I mentioned earlier, I have used EasyMock for a while.  Nearly all of the Java applications I've developed in the last 7 years have used the Spring Framework.  Following the Spring Framework design tenets (Inversion-of-Control, Design-by-Contract) lead to an easily testable codebase using Mock Objects.  I've typically used Mock Objects to simulate database Data Access Objects (DAOs) SOAP Web Service & REST API clients.  Here's an example:


Example

Here's a sample of a JUnit test that uses EasyMock to mock up the access to a database.  The mock is constructed using the interface of the database DAO and is configured with the expectation that the mock method is invoked once with a certain argument.  The test will fail if the mock is not invoked or is invoked with the incorrect parameter.

JUnit Test Class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/testContext.xml" })
public class FooTest {

    @Autowired
    private Foo foo;

    @Test
    public void testSmoke() {
        assertNotNull(foo);
    }

    @Test
    public void testRetrieveMessage_Gold() {
        final String key = "asdf";
        final String outputMessage = "Foo!";

        // create the mock based on the interface (could also be created from a
        // concrete class!)
        final DatabaseDAO databaseDaoMock = EasyMock
                .createMock(DatabaseDAO.class);

        // indicate which methods are expected to be invoked, with what
        // parameters and return values, and the frequency of invocation
        EasyMock.expect(databaseDaoMock.retrieveMessage(key)).andReturn(
                outputMessage);

        // replay the EasyMock mock, indicating that the mock setup is finished
        EasyMock.replay(databaseDaoMock);

        // set the mock on the class-under-test, foo
        foo.setDatabaseDao(databaseDaoMock);

        assertEquals(true, foo.performBusinessLogic(key));

        // verify that the mock was called as expected
        EasyMock.verify(databaseDaoMock);
    }

    public void setFoo(Foo foo) {
        this.foo = foo;
    }

}



Class-Under-Test

public class Foo {

    private DatabaseDAO databaseDao;

    public boolean performBusinessLogic(String key) {
        databaseDao.retrieveMessage(key);

        // do something with the data...

        return true;
    }

    public void setDatabaseDao(DatabaseDAO databaseDao) {
        this.databaseDao = databaseDao;
    }

}


Mocked Interface

public interface DatabaseDAO {

    String retrieveMessage(String key);

}


Conclusion

I hope this post shows how powerful and easy Mock Objects can be.  Your code becomes much easier to test while staying easily maintainable.  Best of luck to you, regardless of which Mock Object framework you choose!

No comments:

Post a Comment