Thursday, January 26, 2006

Use stubs and mocks to convey intent

I've previously stated that only one concrete class should be used per test. I stand by this statement; however, this leads to the question "What should I use for the dependencies of the concrete class under test?"

To illustrate I will use a SqlStatementBuilder class that depends on an instance of IPersistable and an instance of IWhereBuilder. SqlStatementBuilder takes an IPersistable instance which is an object that exposes a name. SqlStatementBuilder uses IWhereBuilder to append a where clause.
class SqlStatementBuilder...
public SqlStatementBuilder(IPersistable instance, IWhereBuilder where)
{
this.instance = instance;
this.where = where;
}

public string CreateFindAllStatement()
{
string mask = "select * from {0} {1}";
return string.Format(mask,instance.Name,where.ToString());
}
Testing SqlStatementBuilder requires you to create instances of both IPersistable and IWhereBuilder. However, if concrete implementations are used any changes to Persistable or WhereBuilder would cascade and could potentially break SqlStatementBuilder tests.

After deciding to use stubs or mocks the decision becomes which of these two options is the best. Some feel that choosing mock or stub usage is exclusive; however, using both is the superior option. A mixture of both solutions can be used to clearly convey the intended usage of a dependency within the current test.

Using a mock to verify the class under test interacts in an expected manner with a dependency is natural. However, tests should verify behavior of a dependency independently of other dependencies. For all dependencies who's behavior is not currently under test a stub is preferred. By using stubs in this manner you convey the message that only the interaction of the class under test and the mocked dependency is important.

For example, if I were testing the CreateFindAllStatement method I would test both that IPersistable.Name was used as the table name and that IWhereBuilder.ToString() was appended to the end of the select statement.
[TestFixture]
class SqlStatementBuilderTests...
[Test]
public void IPersistableNameIsUsedAsTheTableNameInCreateFindAllStatement()
{
IMock mock = new DynamicMock(typeof(IPersistable));
mock.ExpectAndReturn("Name","Foo");
IPersistable persistable = (IPersistable)mock.MockInstance;

SqlStatementBuilder builder = new SqlStatementBuilder(persistable,new StubWhereBuilder());
Assert.AreEqual("select * from Foo ",builder.CreateFindAllStatement());
mock.Verify();
}

[Test]
public void WhereBuilderToStringIsAppendedToTheEndOfAFindAllStatement()
{
IMock mock = new DynamicMock(typeof(IWhereBuilder));
mock.ExpectAndReturn("ToString","where...");
IWhereBuilder where = (IWhereBuilder)mock.MockInstance;

SqlStatementBuilder builder = new SqlStatementBuilder(new StubPersistable(),where);
Assert.AreEqual("select * from where...",builder.CreateFindAllStatement());
mock.Verify();
}
Developing tests in this manner will increase the readability due to the conciseness and clear intent. Additionally, changing the interaction between a class under test and a dependency will only break the tests specific to that dependency. The tests become more durable because a stub's default behavior should be to return a default value or do nothing in the case of a void method. Therefore, stubs will not need to be updated based on a behavioral change.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.