Before I jump into the post, those who aren't familiar with unit testing, JUnit and TDD should probably do a little read up:
As good as a concept it is, it runs into difficulties when your class becomes dependent on several other classes. Thats where mocking comes in, where you create a mock object and make it behave in a particular fashion. The idea here being that when you are testing some class, say ClassA, you expect the dependent classes ClassB and ClassC to behave in a certain fashion. So you create mocks of these classes, making them behave exactly as you expect these classes to at runtime.
I have tried using jMock as library to do the same, but it suffers from the disadvantage that you cannot mock classes. Its pretty straightforward to mock interfaces but unfortunately (although thats how it should be most times), interactions between classes are not always defined via interfaces. So well, the whole testing project was abandoned because of class dependencies.
So let me jump into some code directly. Here's my scenario:
- I have to test a class, say XyzBaseDB, a helper class used for some database extensive rollup programs.
- It takes a class DatabaseConnection, a wrapper database connection class as its constructor.
- The wrapper by itself extends another class that takes care of common connectivity operations like JNDI lookup etc.
- The class implicitly calls the getMetadata() function to get hold of the database metadata.
- On the metadata it uses the database product name to switch between database specific functions (like dual as system table for oracle and sysibm.sysdummy1, etc.)
Now I need to test a method called getSequence that takes two arguments the name of the sequence and the name of the sequence alias as inputs to return the sql statement which when executed will give the next value from the sequence.
So what I really need to do is:
- Create an instance of XyzBaseDB with a mock DatabaseConnection object
- The mock object should return a mocked java.sql.Connection object such that when getDatabaseProductName() method is called on its meta data, it should return "Oracle" as the value
- This will trigger the Oracle specific code within the getSequence(..) method that I want to test
So here's what I did:
- Created a factory class (rather codepro did) to get a specific DatabaseConnection object
- Added the following code within it. Note that I am using the EasyMock class within the classextension directory:
//First mock databasemetadata to mock an Oracle connection DatabaseMetaData md = EasyMock.createMock(DatabaseMetaData.class);
expect(md.getDatabaseProductName()).andReturn("Oracle");
replay(md);
//Mock connection, return mocked metadata for mocked connection Connection conn = EasyMock.createMock(Connection.class);
expect(conn.getMetaData()).andReturn(md);
replay(conn);
//Mock class object, need to use a control here IMocksControl control = EasyMock.createControl(); DatabaseConnection dbConn = control.createMock(DatabaseConnection.class);
expect(dbConn.getConnection()).andReturn(conn);
control.replay(); //replay(dbConn) will fail
return dbConn;
Now any calls on this class will make it work like its an Oracle connection (based on how the code is written). Will keep exploring this to see how it evolves for other use cases as this was pretty straightforward.