18

I want to do mock extension method, but it does not work. How can this be done?

public static class RandomExtensions
{
    public static IEnumerable<int> NextInt32s(this System.Random random, int neededValuesNumber, int minInclusive, int maxExclusive)
    {
        // ...
    }
}

[Fact]
public void Select()
{
    var randomizer = Substitute.For<DefaultRandom>();
    randomizer.NextInt32s(3, 1, 10).Returns(new int[] { 1, 2, 3 });
}
Stachu
  • 5,677
  • 3
  • 30
  • 34
romanshoryn
  • 195
  • 1
  • 1
  • 4
  • 5
    AFAIK you can't mock a extension method with free mocking frameworks. Because extension methods are just static methods, and you can't mock a static method with free mocking frameworks. TypeMock does this I think(but that's beyond the question). – Sriram Sakthivel Feb 24 '15 at 10:56

4 Answers4

3

NSubstitute can not mock extension methods as per Sriram's comment, but you can still pass a mocked argument to an extension method.

In this case, the Random class has virtual methods, so we can mock that directly with NSubstitute and other DynamicProxy-based mocking tools. (For NSubstitute in particular we need to be very careful mocking classes. Please read the warning in the documentation.)

public static class RandomExtensions {
    public static IEnumerable<int> NextInt32s(this System.Random random, int neededValuesNumber, int minInclusive, int maxExclusive) { /* ... */ }
}
public class RandomExtensionsTests {
    [Test]
    public void Select()
    {
        const int min = 0, max = 10;
        var randomizer = Substitute.For<Random>();
        randomizer.Next(min, max).Returns(1, 2, 3);

        var result = randomizer.NextInt32s(3, 0, 10).ToArray();

        Assert.AreEqual(new[] {1, 2, 3}, result);
    }
}
David Tchepak
  • 9,826
  • 2
  • 56
  • 68
  • I tried this but the mock does not return {1,2,3} but goes into the code /* ... */. See [gist](https://gist.github.com/anonymous/a27d3bd572d8abdaf4770805b93eefdb) – Damian Jun 16 '17 at 09:05
  • @Damian It only works if the extension method calls the mocked object. In the gist, `NextInt32s()` would need to call `random.Next(minInclusive, maxInclusive)`. – David Tchepak Jun 16 '17 at 09:12
  • In the dotnet framework Random.Next(min, max) returns an int, not an array of int. Warning new to C#! – Damian Jun 16 '17 at 12:16
  • Ah, ok if you're new to C# NSubstitute might confuse you as it (mis)uses C# syntax in interesting/odd ways. If you implement `NextInt32()` as `for (var i=0; i – David Tchepak Jun 16 '17 at 12:23
  • Hi David! This is a different question but can NSubstitute mock a regular static method too? Is there any way to do that? – Jenix Sep 12 '17 at 08:28
  • @Jenix: no it can't, please see Sriram's comment https://stackoverflow.com/questions/28693698/nsubstitute-mock-extension-method/28708195?noredirect=1#comment45678624_28693698 – David Tchepak Sep 12 '17 at 08:37
0

Yes you can mock if you create an interface such as IRandom and extend the interface instead of the actual implementation. Then you should be able mock the interface in your test class.

public interface IRandom
{   
}

public class Random : IRandom
{     
}

public static class RandomExtensions
{
    public static string NextInt32s(
        this IRandom random, 
        int neededValuesNumber, 
        int minInclusive, 
        int maxExclusive)
    {
    }
}

In your test class add:

IRandom randomizer = Substitute.For<IRandom>();
var result = randomizer.NextInt32s(3,0,10);

By this process you are just mocking the interface not the actual class.

davmos
  • 9,324
  • 4
  • 40
  • 43
0

As an extension to other answers, here is how I got round it.

Imagine there is an interface IDoStuff and there is a library that extends IDoStuff. You have a class MyClass that implements IDoStuff and somewhere someone uses the extension method against the interface. It looks like this;

using System;

interface IDoStuff
{
    string SayHello();
}

class MyClass : IDoStuff
{
    public string SayHello()
    {
        return "Hello";
    }
}

// somewhere someone wrote an extension method for IDoStuff

static class DoStuffExtensions
{
    static string SayHelloToBob(this IDoStuff other)
    {
        return other.SayHello() + " Bob";
    }
}

class UserOfIDoStuff
{
    void UseIDoStuff(IDoStuff incoming)
    {
        Console.WriteLine(incoming.SayHelloToBob());
    }
}

You want to mock IDoStuff but you cannot mock the extension method SayHelloToBob. What you can do is to create another interface that implements IDoStuff but also includes SayHelloToBob.

interface IDoStuffWithExtensions : IDoStuff
{
    string SayHelloToBob();
}

class MyClass : IDoStuffWithExtensions
{
    public string SayHello()
    {
        return "Hello";
    }

    // Wrap / internalise the extension method
    public string SayHelloToBob()
    {
        return DoStuffExtensions.SayHelloToBob(this);
    }
}

class UserOfIDoStuff
{
    void UseIDoStuff(IDoStuffWithExtensions incoming)
    {
        Console.WriteLine(incoming.SayHelloToBob());
    }
}

Now you can happily mock IDoStuffWithExtensions.

Steztric
  • 2,832
  • 2
  • 24
  • 43
-4

According to SOLID principle dependence inversion defines that lower level model should not be depended high level model but depended on abstract like interface and mocking concept is mainly used to mock interface so that low level model is not tested.

  • Yes, but if I define interface for DefaultRandom and mock him, then it doesn't see extension method NextInt32s. How can I do? – romanshoryn Feb 24 '15 at 14:56