Search code examples
c#moqfunc

Mock an interface with a property of Func type and invoke it from test


An interface that I need to mock declares a Func delegate as a property instead of an event. I am quite used to mocking events by using the SetupAdd(), Raise(), etc. methods of Mock. However, they don't work for a delegate.

The example below shows my situation.

The interface to be mocked for test (third-party, outside my control):

public interface IResource {
  Func<string, Response> QuickNotification {get; set;}
}

The class under test:

public class Subject {
  private readonly IResource _resource;
  Subject(IResource resource) // .ctor
  {
    _resource = resource;
    _resource.QuickNotification += NotificationSink;
  }
  private Response NotificationSink(string message)
  {
    // do something..
    return new Response();
  }
}

Test:

using Moq;

public class SubjectTests {
  [Test] public void Test1()
  {
    var mockResource = new Mock<IResource>();
    mockResource.Setup(x => x.QuickNotification).Returns(\\??WHAT TO DO HERE??//);

    var sut = new Subject(mockResource.Object);
    var response = mockResource.Object.QuickNotification.Invoke("test message");
    Assert.IsNotNull(response);
  }
}

In Returns(), I have tried various options ranging from doing SetupAdd(), intercepting the attached handler in Callback and finally returning a Mock<Func<string, Response>>::Object, to returning an actual Func<..,..> initialized to some value, to many things in between.

However, none of these work. For example, with

var mockFunc = new Mock<Func<string, Response>>();
Func<string, Response> subjectCallback = null;
mockFunc.SetupAdd(_ => _ += It.IsAny<Func<string, Response>>())
        .Callback<Func<string, Response>>((cb) => subjectCallback = cb);
...
var response = subjectCallback.Invoke("test message");

in SetupAdd(), I see the exception:

System.InvalidCastException : Unable to cast object of type 'System.Func`2[..,..]' to type 'Moq.Internals.IProxy'.

Solution

  • The solution turns out be quite simple, but maybe either not well documented or not discovered by me. This works:

    [Test] public void Test1()
    {
      var mockResource = new Mock<IResource>();
      mockResource.SetupProperty(x => x.QuickNotification, (Func<string, Response>)null);
    
      var sut = new Subject(mockResource.Object);
      var response = mockResource.Object.QuickNotification.Invoke("test message");
      Assert.IsNotNull(response);
    }
    

    Note the use of SetupProperty and it returning null.

    PS: make sure to not use create the mock as new Mock<IResource>() { DefaultValue = DefaultValue.Mock } for the above to work. If you do this, Moq sets up the Func property already to return a Mock<Response> object.