Search code examples
c#rhino-mocks

Stubbing a static extension method seems to work in Rhino Mocks unless I cast the return variable. Why?


I am able to stub a static extension method using Rhino Mocks but if I cast the return value to another type, I get an error. Why?

using Rhino.Mocks;

public interface INumberGenerator
{
    double GetDouble();
}

static class NumberGeneratorExtensionMethods
{
    public static double GetTheDouble(this INumberGenerator input)
    {
        return input.GetDouble();
    }

    public static decimal GetTheDoubleCastToDecimal(this INumberGenerator input)
    {
        return (decimal) input.GetDouble();
    }
}

class MockExample
{
    public void TriggerTheError()
    {
        var stub = MockRepository.GenerateStub<INumberGenerator>();

        // This works
        stub.Stub(obj => obj.GetTheDouble()).Return(1.2d);

        // This throws the error
        stub.Stub(obj => obj.GetTheDoubleCastToDecimal()).Return(1.2m);
    }
}

Here is the error:

System.InvalidOperationException : Type 'System.Decimal' doesn't match the return type 'System.Double' for method 'INumberGenerator.GetDouble();'


Solution

  • Warning: this is really a suspicion more than anything else

    The problem is that you're not really stubbing the extension methods at all - you're stubbing GetDouble in both cases.

    I haven't looked at the code for Rhino Mocks for a while, but I suspect that the Stub method is basically saying:

    • Get ready for some calls on the mock!
    • Call the delegate passed in as an argument
    • Note which calls were made

    That means you're effectively doing this:

    canGetDecimal.Stub(ng => ng.GetDouble()).Return(1.2d);
    canGetDecimal.Stub(ng => (decimal) ng.GetDouble()).Return(1.2m);
    

    At that point, it would notice that you called GetDouble - but then you're trying to set the return value to 1.2m, which is invalid.

    You could validate this pretty easily, with some logging. Add a log line to GetTheDoubleCastToDecimal and then split out the Stub call from the Return call:

    Console.WriteLine("Before Stub");
    var stubCall = canGetDecimal.Stub(obj => obj.GetTheDoubleCastToDecimal();
    Console.WriteLine("After Stub");
    stubCall.Return(1.2m);
    

    I strongly suspect you'll find that whatever logging you add into the extension method is still logged between "Before Stub" and "After Stub" - showing that the extension method isn't being mocked out.

    Moral: don't try to mock/stub extension methods. They're not polymorphic; they're just static methods, and it would be pretty tricky to fake them without deep wizardry. Only try to fake out genuinely polymorphic operations.