Martin Brown
Martin Brown

Reputation: 25310

How to mock the raising of a delegate on an interface that only has a setter?

I'm trying to mock an interface using Moq, but I can't see how to do it. The interface looks something like this:

public delegate void MyDelegate(int i);

public interface MyInterface
{
    void Method(int i);

    MyDelegate MyDelegate { set; }
}

I am testing a component that takes an object with this interface as a dependency. I would like to be able to test an interaction where the delegate is raised when the method is called, but I can't see how to do it. I know this is a slightly strange way to design an interface, but I don't have control over that.

Suppose I have a class like this to test:

class SystemUnderTest
{
    int i = 0;

    readonly MyInterface myInterface;

    public SystemUnderTest(MyInterface myInterface)
    {
        this.myInterface = myInterface;
        this.myInterface.MyDelegate = DelegateHandler;
    }

    public int Run(int input)
    {
        this.myInterface.Method(input);
        return i;
    }

    void DelegateHandler(int i)
    {
        this.i = i;
    }
}

I've tried a test like this, but it gives an exception when setting up the mock. "ArgumentException: Could not locate event for attach or detach method Void set_MyDelegate(ConsoleApp1.MyDelegate)."

static void Main(string[] args)
{
    // Arrange
    Mock<MyInterface> mock = new Mock<MyInterface>();

    mock
        .Setup(m => m.Method(It.IsAny<int>()))
        .Raises(m => m.MyDelegate = null, 5);

    // Act
    var sut = new SystemUnderTest(mock.Object);
    var result = sut.Run(5);

    // Assert
    Trace.Assert(result == 5);
}

Upvotes: 3

Views: 794

Answers (2)

Johnny
Johnny

Reputation: 9519

You could so to say bridge your SystemUnderTest.DelegateHandler through a MyDelegate local variable. Try something like this:

MyDelegate del = null;

var mock = new Mock<MyInterface>();
mock.SetupSet(m => m.MyDelegate = It.IsAny<MyDelegate>())
    .Callback<MyDelegate>(d => del = d);
mock.Setup(m => m.Method(It.IsAny<int>()))
    .Callback<int>(i => del.Invoke(i));

Then actually your SystemUnderTest.DelegateHandler method will be called every time you invoke mock.Method.

Upvotes: 6

Paul Suart
Paul Suart

Reputation: 6713

I think the fundamental flaw in your approach is you're trying to mock something that doesn't actually occur in your code - namely that calling the void Method(int i) subsequently calls your delegate. You can't use Raises as that's for events.

I think you need a "stub" implementation of MyInterface, and you don't need to use Moq at all. Here's my code that passes the assertion:

using System.Diagnostics;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            // Arrange
            var stub = new StubOfMyInterface();

            // Act
            var sut = new SystemUnderTest(stub);
            var result = sut.Run(5);

            // Assert
            Trace.Assert(result == 5);
        }
    }

    public delegate void MyDelegate(int i);

    public interface MyInterface
    {
        void Method(int i);

        MyDelegate MyDelegate { set; }
    }

    public class StubOfMyInterface : MyInterface
    {
        public void Method(int i)
        {
            MyDelegate?.Invoke(i);
        }

        public MyDelegate MyDelegate { get; set; }
    }

    class SystemUnderTest
    {
        int i = 0;

        readonly MyInterface myInterface;

        public SystemUnderTest(MyInterface myInterface)
        {
            this.myInterface = myInterface;
            this.myInterface.MyDelegate = DelegateHandler;
        }

        public int Run(int input)
        {
            this.myInterface.Method(input);
            return i;
        }

        void DelegateHandler(int i)
        {
            this.i = i;
        }
    }   
}

Upvotes: 2

Related Questions