Search code examples
.netmockingrhino-mocks

With Rhino Mocks how do I intercept the call to a single property on an interface while passing everything else on to the “default implementation”?


I have an interface IComplex with many methods and properties, I wish to create a “mock” that makes the “Config” property return an object of my choose, while passing all other calls onto a “real” instance of IComplex.

Just to make this a bit harder we are still using C# V2!


Solution

  • As far as I know, there isn't a feature to configure many methods of mock at once. You need to specify all the methods to forward them to the other implementation...

    ... unless you write some reflection code to configure the mock.

    Here is some (working) code. It uses C# 3.0, but the main part is "old style" Rhino Mocks stuff which had been written for C# 2.0.

    public static class MockExtensions
    {
    
        public static void ForwardCalls<T>(this T mock, T original)
        {
            mock.BackToRecord();
            Type mockType = typeof(T);
            var methods = mockType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                .Union(mockType.GetInterfaces().SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.Instance)));
            foreach (MethodInfo method in methods)
            {
                List<object> args = new List<object>();
                foreach (var arg in method.GetParameters())
                {
                    args.Add(CreateDefaultValue(arg.ParameterType));
                }
                method.Invoke(mock, args.ToArray());
                var myMethod = method;
                if (method.ReturnType == typeof(void))
                {
                    LastCall
                        .IgnoreArguments()
                        // not Repeat.Any to allow overriding the value
                        .Repeat.Times(int.MaxValue)
                        .WhenCalled(call => myMethod.Invoke(original, call.Arguments));
                }
                else
                {
                    LastCall
                        .IgnoreArguments()
                        // not Repeat.Any to allow overriding the value
                        .Repeat.Times(int.MaxValue)
                        .WhenCalled(call => call.ReturnValue = myMethod.Invoke(original, call.Arguments))
                        .Return(CreateDefaultValue(myMethod.ReturnType));
                }
            }
            mock.Replay();
        }
    
        private static object CreateDefaultValue(Type type)
        {
    
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            else
            {
                return Convert.ChangeType(null, type);
            }
        }
    
    }
    

    Usage:

    [TestClass]
    public class TestClass()
    {
        [TestMethod]
        public void Test()
        {
            var mock = MockRepository.GenerateMock<IList<int>>();
            List<int> original = new List<int>();
    
            mock.ForwardCalls(original);
    
            mock.Add(7);
            mock.Add(8);
            Assert.AreEqual(2, mock.Count);
            Assert.AreEqual(7, mock[0]);
            Assert.AreEqual(8, mock[1]);
    
            //fake Count after ForwardCalls, use Repeat.Any()
            mock.Stub(x => x.Count)
                .Return(88)
                .Repeat.Any(); // repeat any needed to "override" value
    
            // faked count
            Assert.AreEqual(88, mock.Count);
        }
    }