Test doubles (mocks/stubs) against method chaining or fluent interface syntax
I have code under test that basically looks like this (the specific code isn't important to the question. It is just here for explanatory purposes):
public ICollection<Product> GetByCategory(string category, ISession session)
{
return session
.CreateCriteria(typeof(Product))
.Add(Restrictions.Eq("Category", category))
.List<Product>();
}
This uses method chaining (and the solution I'm loo开发者_JAVA百科king for would also apply to fluent interface syntax).
I am not interested in finding solutions for just this specific example, I am interested in solving a more general problem. In this example, I'd like to only add an expectation for CreateCriteria. If I do this, though, I get a NullReferenceException, even if I have the CreateCriteria return a stub, because the Add method returns null.
I'd like my test to continue to work, even if additional methods are chained, or the Add method gets removed.
Is there a general trick to lowering the number of test doubles/expected calls to just the ones I want to assert against, when using method chaining?
A solution I can think of would be to make a T4 template that enumerates all methods on a type, and creates a stub with expectations that give a different default return values. But I am wondering if there are simpler options.
I am using Rhino.Mocks, but a general solution would be even more appreciated.
A possible approach would be to wrap the mock object in a DynamicProxy which always returns this
for the methods which are part of the fluent API and have no recorded expectations. It delegates to the normal mock object for methods for which expectations have been recorded (or are not part of the fluent interface).
Detecting which methods have expectations defined will of course be highly MockLibrary dependent. The non-fluent methods can be easily tested for using introspection.
Maybe one of the libraries has already built this in?
I needed something like this for the NHibernate IQuery
interface. I used Castle.DymanicProxy and Rhino.Mocks with the following implementation of a Fake IRepository...
internal class FakeRepository<TTypeOfModel> : IRepository<TTypeOfModel>
{
....
public IQuery GetNamedQuery(string queryName)
{
return MockFactory.MockWithMethodChainingFor<IQuery>();
}
....
}
internal static class MockFactory
{
public static T MockWithMethodChainingFor<T>()
where T : class
{
var generator = new ProxyGenerator();
return generator.CreateInterfaceProxyWithTargetInterface(
MockRepository.GenerateMock<T>(),
new MethodChainingMockInterceptor<T>());
}
}
internal class MethodChainingMockInterceptor<T> : StandardInterceptor
{
protected override void PerformProceed(IInvocation invocation)
{
if ((Type)invocation.Method.ReturnType == typeof(T))
{
invocation.ReturnValue = invocation.Proxy;
}
else
{
base.PerformProceed(invocation);
}
}
}
精彩评论