Channels ▼
RSS

Web Development

Testing OO Systems, Part 2: Testing Servlet Implementations


Let's make all this a bit more concrete by looking at how you'd mock database access in a servlet. Listing Five defines two classes. The JDBCSample class just shows you a typical JDBC query. You get a connection from a DriverManager and use that connection to make a Statement. You execute the query against the Statement, which returns a result set. The while loop just looks at all the rows in the returned result set.

Now, let's say that we don't want to use the real database at all. Looking at the earlier code, the DriverManager is a perfectly good Factory object as it stands, so if we change the behavior of getConnection() to return our own mock connection, we can control the entire access process.

Listing Five

package com.holub.tests;

import static org.junit.Assert.*;

import java.sql.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

class JDBCSample
{
	public static void someMethod() throws Exception
	{
		// ...

		Connection connection = null;
		Statement  statement  = null;
		try
		{	connection = DriverManager.getConnection( "connectionURL", "username", "password" );

			statement = connection.createStatement();
		
			ResultSet result = statement.executeQuery( "select something from SomeTable where someColumn=fred"  );

			while( result.next() )
			{	String someColumn = result.getString("ColumnName");
				//...
				System.out.println( someColumn );
			}
		}
		finally
		{
		  try{ if(statement != null) statement.close(); }catch(Exception e){}
		  try{ if(connection!= null) connection.close();}catch(Exception e){}
		}
	}
}

@RunWith(PowerMockRunner.class)
@PrepareForTest( {DriverManager.class, JDBCSample.class, JDBCTest.class} )
public class JDBCTest extends PowerMockito
{
	@Test
	public void doTest() throws Exception
	{
		// This answer is used for the result set's next() method. It simulates a one-row result set.
		class MyAnswer implements Answer<Boolean>
		{
			private int timesCalled = 0;
			@Override public Boolean answer(InvocationOnMock invocation) throws Throwable
			{	return ++timesCalled <= 1;
			}
		}
		
		ResultSet mockResultSet = mock( ResultSet.class );
		when( mockResultSet.next() ).thenAnswer( new MyAnswer() );
		when( mockResultSet.getString( Mockito.anyString() )).thenReturn("TestResult");
		
		Statement mockStatement = mock( Statement.class );
		when( mockStatement.executeQuery( Mockito.anyString() )).thenReturn( mockResultSet );
		
		Connection mockConnection = mock( Connection.class );
		when( mockConnection.createStatement() ).thenReturn( mockStatement );
		
		mockStatic( DriverManager.class );
		when(DriverManager.getConnection( Mockito.anyString(), Mockito.anyString(), Mockito.anyString()))
							.thenReturn( mockConnection );
		
		JDBCSample.someMethod();
	}
}

The JDBCTest class at the bottom of Listing Five does exactly that. To avoid forward references, the code is sort-of upside down, so let's look at it in reverse order. The lines

    mockStatic( DriverManager.class );
    when(DriverManager.getConnection( Mockito.anyString(), Mockito.anyString(), Mockito.anyString()))
                        .thenReturn( mockConnection );

change the behavior of DriverManager.getConnection() to return a mockConnection rather than a real one. The next line up:

    Connection mockConnection = mock( Connection.class );
    when( mockConnection.createStatement() ).thenReturn( mockStatement );

creates that mock connection. This particular mock returns a mockStatement when a statement is requested. The preceding couple of lines do a similar thing with the result set. They override the behavior of executeQuery() to return a mock result set:

    ResultSet mockResultSet = mock( ResultSet.class );
    when( mockResultSet.next() ).thenAnswer( new MyAnswer() );
    when( mockResultSet.getString( Mockito.anyString() )).thenReturn("TestResult");
        
    Statement mockStatement = mock( Statement.class );
    when( mockStatement.executeQuery( Mockito.anyString() )).thenReturn( mockResultSet );

The only subtly is that the while loop that goes through the result set has to be modified to simulate a one-row result set. PowerMock does that with the following "answer" object:

    class MyAnswer implements Answer<Boolean>
    {
        private int timesCalled = 0;
        @Override public Boolean answer(InvocationOnMock invocation) throws Throwable
        {   return ++timesCalled <= 1;
        }
    }

The thenAnswer(...) call tells the mocking framework to call the answer object's answer(...) method every time the associated method (next()) is called. This answer object returns true on the first call and false on all subsequent calls.

Once all this infrastructure is in place, we can run the test by calling

    JDBCSample.someMethod();

someMethod() will use all our mocks instead of the real objects, and the code will print the string "TestResult" just as if it had looked up something in the database and fetched the value "TestResult" from the actual database. Our test doesn't use a database at all, however.

Conclusion

This basic technique shows you how to use a mock framework to create objects both inside and outside the donut. For the inside objects, use Factories to create objects rather than new, and then mock the create() methods as necessary to produce your test objects rather than the real ones. I've used mocks for the examples in this article, but you could also use a "spy" object to monitor what happens to the donut-hole object. That is, your mock create() would call the real factory to create objects, but then wrap that real object with a "spy" whose methods just call identical methods in the wrapped real object. (A "spy" is a Gang-of-Four-type Decorator pattern that, for the most part, just passes requests through to the decorated object.) The spy can record what happens to it, however, so your test can transparently monitor how the contained object was used and then validate that the contained object was used in the expected way.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV