Given an object, I would like to create a mock that implements the interface of the object and mocks one method, but forwards the rest of the methods to the real object, not the base class.
For example:
ISqlUtil sqlUtil = GetTheRealSqlUtilObjectSomehow(...);
var mock = new Mock<ISqlUtil>();
mock.Setup(o => o.SpecialMethodToBeMocked(...)).Returns<...>(...)
// Here I would like to delegate the rest of the methods to the real sqlUtil object. How ?
So, in the example I want to mock just ISqlUtil.SpecialMethodToBeMocked
and forward the rest of methods/properties to the existing instance sqlUtil
.
Is it possible in Moq.NET ?
EDIT 1
It should work for generic methods as well.
You can't do this with Moq out of the box. However, I think you can achieve basically what you want if you go down to the next layer and use Castle DynamicProxy directly (which is what's underneath Moq).
So, given the following base code to simulate your issue (essentially, an interface, a concrete implementation and a factory because the concrete is hard to make/setup):
public interface ISqlUtil {
T SomeGenericMethod<T>(T args);
int SomeMethodToIntercept();
}
public class ConcreteSqlUtil : ISqlUtil {
public T SomeGenericMethod<T>(T args){
return args;
}
public int SomeMethodToIntercept() {
return 42;
}
}
public class SqlUtilFactory {
public static ISqlUtil CreateSqlUtil() {
var rVal = new ConcreteSqlUtil();
// Some Complex setup
return rVal;
}
}
You can then have the following test:
public void TestCanInterceptMethods() {
// Create a concrete instance, using the factory
var coreInstance = SqlUtilFactory.CreateSqlUtil();
// Test that the concrete instance works
Assert.AreEqual(42, coreInstance.SomeMethodToIntercept());
Assert.AreEqual(40, coreInstance.SomeGenericMethod(40));
// Create a proxy generator (you'll probably want to put this
// somewhere static so that it's caching works if you use it)
var generator = new Castle.DynamicProxy.ProxyGenerator();
// Use the proxy to generate a new class that implements ISqlUtil
// Note the concrete instance is passed into the construction
// As is an instance of MethodInterceptor (see below)
var proxy = generator.CreateInterfaceProxyWithTarget<ISqlUtil>(coreInstance,
new MethodInterceptor<int>("SomeMethodToIntercept", 33));
// Check that calling via the proxy still delegates to existing
// generic method
Assert.AreEqual(45, proxy.SomeGenericMethod(45));
// Check that calling via the proxy returns the result we've specified
// for our intercepted method
Assert.AreEqual(33, proxy.SomeMethodToIntercept());
}
The method interceptor looks like this:
public class MethodInterceptor<T> : Castle.DynamicProxy.IInterceptor {
private T _returns;
private string _methodName;
public MethodInterceptor(string methodName, T returns) {
_returns = returns;
_methodName = methodName;
}
public void Intercept(IInvocation invocation) {
if (invocation.Method.Name == _methodName) {
invocation.ReturnValue = _returns;
}
else {
invocation.Proceed();
}
}
}
Essentially, the interceptor checks if the method being called matches the one you're interested in and if so, returns the stored return value. Otherwise, it calls Proceed
, which delegates the method call onto the concrete object supplied when the proxy was created.
The example code uses strings rather than lambdas to specify the method to intercept, obviously this could be changed (exercise for the reader). Also, this isn't using Moq, so you lose the Setup
, Returns
and Verify
elements, which are replaced by the Interceptor, so this may be too far away from what you're after to be useful, however depending what your code really looks like it may be a viable alternative approach.
If you're unable to mock the class and delegate calls to the base by default, then you'll have to manually wire up the delegation to your separate instance.
var util = GetSqlUtil();
var mockUtil = new Mock<ISqlUtil>(MockBehavior.Strict);
mockUtil.Setup(x => x.SomeCall(...)).Returns<...>(args => util.SomeCall(args));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With