Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

rhino-mocks

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();'

like image 919
mattalxndr Avatar asked Dec 21 '22 03:12

mattalxndr


2 Answers

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.

like image 160
Jon Skeet Avatar answered Mar 08 '23 22:03

Jon Skeet


There is possibility to stub extension method or any other static method without any framework. This requires a bit of additional afford.

public static class MyExtensions
{
    public static Func<int,int, int> _doSumm = (x, y) => x + y;

    public static int Summ(this int x, int y)
    {
        return _doSumm(x, y);
    }
}

It allows you to substitute the implementation. You can just change value of _doSumm field.

like image 30
dmigo Avatar answered Mar 08 '23 23:03

dmigo