4.8 Mock Objects

     

Applications often use interfaces to external objects such as databases, web servers, network services, or hardware devices. Sometimes you must write and test code to interface with objects before they are actually available. Even when the external object is available in the development environment, using it in testing may involve lots of time-consuming , fragile set-up effort, such as loading test data, running services, or placing hardware in a known state. Mock objects are a way of dealing elegantly with this type of situation.

A mock object is a simulation of a real object. Mocks implement the interface of the real object and behave identically with it, to the extent necessary for testing. Mocks also validate that the code that uses them does so correctly. To pass the mock's validation, other objects must call the correct methods , with the expected parameters, in the expected order. A test object that simply stands in for a real object without providing such verification is not a mock; it is a stub.

Databases are commonly mocked objects. Code that interfaces to a database clearly is important to test. To be tested realistically , the code must be able to perform database operations such as opening and closing connections, reading and writing data, and performing transactions. However, running a live database in the development environment can be a pain. Tests often require that the database is in a specific state or that it contains a specific set of test data. If multiple developers run tests simultaneously , their database operations may interfere.

Mocking the database makes having an actual database unnecessary for testing. The mock has the same interface as the actual database object and the same behavior from the perspective of the client software, but it doesn't need to actually contain anything but a minimal implementation and possibly some test data. Once the database mock is created, it becomes much simpler to write tests that assume that the database is in various states. Testing becomes faster and easier without the overhead of interfacing with an actual database engine.

To illustrate this, let's create a mock object representing a database connection object. An interface called DBConnection represents a database connection, as shown in Example 4-14.

Example 4-14. The interface DBConnection, representing a database connection
 DBConnection.java public interface  DBConnection  {    void connect( );    void close( );    Book selectBook( String title, String author ); } 

The class LibraryDB retrieves Book s from a database using DBConnection . It is shown in Example 4-15.

Example 4-15. The database interface LibraryDB
 LibraryDB.java public class  LibraryDB  {    private  DBConnection  connection;    public  LibraryDB  ( DBConnection c ) {       connection = c;    }    Book  getBook  ( String title, String author ) {       connection.connect( );       Book book = connection.selectBook( title, author );       connection.close( );       return book;   } } 

We would like to build a unit test for LibraryDB , but we don't have an actual database yet. So, we'll mock DBConnection as shown in Example 4-16.

Example 4-16. The mock object MockDBConnection
 MockDBConnection.java public class  MockDBConnection  implements  DBConnection  {    private boolean  connected  = false;    private boolean  closed  = false;    public void  connect( )  { connected = true; }    public void  close( )  { closed = true; }    public Book  selectBook  ( String title, String author ) {       return null;    }    public boolean  validate( )  {       return connected && closed;    } } 

MockDBConnection implements the public interface of DBConnection , so it can be used in the interface's place. MockDBConnection uses the attributes connected and closed to record that the connect( ) and close() methods have been called. The validate( ) method verifies the connection's state by checking these flags. So, the expectation set by the mock is that code using DBConnection must call both connect( ) and close( ) .

The test class LibraryDBTest is shown in Example 4-17.

Example 4-17. The test class LibraryDBTest
 LibraryDBTest.java import junit.framework.*; import java.util.*; public class  LibraryDBTest  extends TestCase {    public void  testGetBook( )  {       MockDBConnection mock =  new MockDBConnection( )  ;       LibraryDB db =  new LibraryDB( mock )  ;       Book book = db.getBook( "Cosmos", "Carl Sagan" );  assertTrue( mock.validate( ) )  ;    } } 

The test method testGetBook( ) creates an instance of MockDBConnection , uses it to construct a LibraryDB , and then calls the LibraryDB method getBook( ) . The success of the test depends on the result of the mock's validate( ) function. If the mock is in the expected state, its validation succeeds and the test passes . The mock object verifies the expected sequence of calls to the database connection and validates that LibraryDB is interacting with it correctly. It also allows LibraryDB and DBConnection to be tested without an actual database.

More sophisticated mock objects go beyond simply setting flags for each method called by recording the arguments provided for method calls, the order of calls, and other details of the method's state. In this way, mock objects can perform sophisticated validation of interobject interactions.

Mock objects are a deep topic, covered by numerous web sites, books, and online groups. Also, a variety of tools are available to support mock object development for various domains and languages, including EasyMock, jMock, and MockRunner.



Unit Test Frameworks
Unit Test Frameworks
ISBN: 0596006896
EAN: 2147483647
Year: 2006
Pages: 146
Authors: Paul Hamill

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net