Back in 2003 Chris Sells wrote a blog post called
”Sealed
Sucks” on the pros and cons of sealing a class. When I recently wrote some
tests for an OR-mapping framework, I shared Chris’ feelings on the
sealed
keyword. The reason was simple, I had a class which mapped objects to stored
procedures that exposed a single method to persist objects, and I had to ensure
that the correct procedure was called, the correct parameters where passed to
the procedure and so forth. The OR-mapper’s constructor expected a
SqlConnection object as its parameter, so I figured that I’d just pass it a
mocked
SqlConnection.
NMock has been my tool of choice for mocking objects, but since SqlConnection
is a sealed class you’ll get an exception if you try to mock it using NMock. Writing
a mock SqlConnection by hand is neither an option, because you won’t be able to
cast it to a SqlConnection.
Luckily, I came across a nifty mocking framework called TypeMock.NET which has the ability to mock any member
of any concrete class, no matter whether the members are non-virtual or private,
or the class is sealed. TypeMock.NET uses AOP-like techniques at the IL level
to intercept usages of any type and member. It does this by using the .NET Framework’s
profiler API to monitor execution. When the CLR loads a method, TypeMock.NET replaces
the IL with calls to the mocked code.
Below is an example of how to assert that an a SqlParameter
with the appropriate customer id is added to a SqlCommand’s Parameters
collection when a Customer object is persisted.
[TestFixture]
public class DataServiceTest
{
[SetUp]
public void
Setup()
{
MockManager.Init();
}
[Test]
public void
TestDataService()
{
Customer
customer=new Customer();
customer.Id="42";
string
connectionString="Mock Connection String";
Mock
mockSqlConnection=MockManager.Mock(typeof(SqlConnection));
mockSqlConnection.ExpectConstructor().Args(connectionString);
Mock
mockSqlCommand=MockManager.Mock(typeof(SqlCommand));
Mock
mockSqlParameterCollection=MockManager.Mock(typeof(SqlParameterCollection));
mockSqlParameterCollection.ExpectAndReturn("Add",1).Args("CustomerID",customer.Id);
mockSqlCommand.ExpectCall("ExecuteNonQuery",1);
mockSqlConnection.ExpectAndReturn("CreateCommand",new
SqlCommand());
DataService
dataService=new DataService(new SqlConnection(connectionString));
dataService.SaveObject(customer);
}
[TearDown]
public void
TearDown()
{
MockManager.Verify();
}
}
There are a couple of things to take note of in the above
example. When I register mock objects for the SqlConnection, SqlCommand and
SqlParameterCollection types, the actual mock objects aren’t created until I instantiate
a new instance of either of the classes. This is the reason why I return an
actual SqlCommand instead of a mock object in my mock implementation of
SqlConnection.CreateCommand. Further, I do not need to manually create an
instance of the SqlParameterCollection, because this class will automatically
be mocked when the SqlCommand creates is Parameters collection.
Finally, I have to call the MockManger.Verify method to
assert that all of my expectations have been fulfilled. This is akin to using
Assert statements in a regular NUnit test.
Thanks to TypeMock.NET sealed doesn’t suck as much as it
used to.