Freigeben über


Evolution of a hand rolled fake - part 4

Another hard problem when it comes to creating fakes is when the interface contain overloads (i.e. same method name but different parameters) like this:

  1: public interface IYetAnotherInterface
 2: {
 3:     int DoSomething();
 4:     int DoSomething(int x);
 5: }

The Fakes utility in VS11 will generate properties like DoSomethingInt32 and DoSometingInt32Int32. Not great names IMHO. So there are three ways I deal with this types of interfaces. The first way, if I have control over the interface and the overloads or more for convenience than anything else, then I avoid overloads in the interface. I make the overloads extension methods on the interface instead. In the example above I would only have the last method. If I have an asynchronous and synchronous method on the same interface I try to just have the asynchronous one and then make the synchronous one an extension method. This way my fakes can be implemented the same way as before.

The second way to deal with it is to let my fake just have one handler and make the overloads with fewer arguments just call the one with most arguments. This works in a lot of cases but makes the fake a little smarter than it should really be in some cases. Here is an example of this approach:

  6: public class FakeYetAnotherInterface : IYetAnotherInterface
 7: {
 8:     public Func<int, int> DoSomething { get; set; }
 9:  
 10:     int IYetAnotherInterface.DoSomething()
 11:     {
 12:         Assert.IsNotNull(this.DoSomething, "Unexpected call to DoSomething");
 13:         return this.DoSomething(0);
 14:     }
 15:  
 16:     int IYetAnotherInterface.DoSomething(int x)
 17:     {
 18:         Assert.IsNotNull(this.DoSomething, "Unexpected call to DoSomething");
 19:         return this.DoSomething(x);
 20:     }
 21: }

My third option to deal with this is still to have one one handler for all my overloads but at the same time make sure I can easily know which one is being called. In this case I use a special class for the arguments:

  22: public class FakeArgument<T>
 23: {
 24:     private T value;
 25:  
 26:     public FakeArgument()
 27:     {
 28:     }
 29:  
 30:     public FakeArgument(T value)
 31:     {
 32:         this.Value = value;
 33:     }
 34:  
 35:     public bool HasValue { get; private set; }
 36:  
 37:     public T Value
 38:     {
 39:         get
 40:         {
 41:             Assert.IsTrue(this.HasValue, "Argument not set");
 42:             return this.value;
 43:         }
 44:  
 45:         private set 
 46:         { 
 47:             this.value = value;
 48:             this.HasValue = true;
 49:         }
 50:     }
 51:  
 52:     public static implicit operator T(FakeArgument<T> arg)
 53:     {
 54:         return arg.Value;
 55:     }
 56: }

And this is the fake using that class:

  57: public class FakeYetAnotherInterface : IYetAnotherInterface
 58: {
 59:     public Func<FakeArgument<int>, int> DoSomething { get; set; } 
 60:  
 61:     int IYetAnotherInterface.DoSomething()
 62:     {
 63:         Assert.IsNotNull(
 64:             this.DoSomething, "Unexpected call to DoSomething()");
 65:         return this.DoSomething(new FakeArgument<int>());
 66:     }
 67:  
 68:     int IYetAnotherInterface.DoSomething(int x)
 69:     {
 70:         Assert.IsNotNull(
 71:             this.DoSomething, "Unexpected call to DoSomething({0})", x);
 72:         return this.DoSomething(new FakeArgument<int>(x));
 73:     }
 74: }

And last a test using that fake:

  75: [TestMethod]
 76: public void UsingFake5()
 77: {
 78:     var fakeThing = new FakeAnotherInterface
 79:         {
 80:             DoSomething = arg =>
 81:                 {
 82:                     Assert.IsFalse(arg.HasValue, "Wrong overload called");
 83:                     return 42;
 84:                 }
 85:         };
 86:     IAnotherInterface thing = fakeThing;
 87:     Assert.AreEqual(42, thing.DoSomething());
 88: }

This is however my last resort to deal with overloads in the interfaces needing faking. The first two options are definitely my preferred method.