JasonWilczak
JasonWilczak

Reputation: 2403

Mocking Service Channels

I am writing some unit tests for code that was already written. I ran into a situation that, surprisingly, I haven't run into before.

In short, I need to test a Controller that takes in a service dependency via DI and in the Index action a method is called on that service to return data for the view.

My issue is that I can't seem to mock out the service dependency. I tried searching a bit but couldn't find anything that directly dealt with my issue (or my lack of understanding).

Below is an example of the controller:

public partial class SomeController : Controller
{
    private readonly IServiceChannel<ISomeService> _someChannel;

    public SomeController(IServiceChannel<ISomeService> someChannel)
    {
        _someChannel= someChannel;
    }

    public virtual ActionResult Index()
    {
        var response = _someChannel.Fetch(someService => someService .GetSomeData(
            new GetSomeDataRequest
            {
                Id= StaticData.Id,
                All = true
            }
        ));

        var model = new IndexViewModel
        {
            AllData= response.AllData
        };

        return View(model);
    }
}

As you can see, DI injects an instance of IServiceChannel which has a "Fetch" method. That method then gives you access to ISomeService and from there you can call any method on the interface with the necessary params.

I attempted to write a test around this using the below logic:

[TestFixture]
public class SomeControllerTests
{
    private IServiceChannel<ISomeService> _someChannel;
    [SetUp]
    public void Setup()
    {
        var someChannelMoc = new Mock<IServiceChannel<ISomeService>>();
        someChannelMoc.Setup(
            i => i.Fetch(service => service.GetSomeData(It.IsAny<GetSomeDataRequest>())))
            .Returns(new GetSomeDataResponse());

        _someChannel= someChannelMoc.Object;
    }

    [Test]
    public void IndexTest()
    {
        var controller = new SomeController(_someChannel);
        var result = controller.Index();
        Assert.IsNotNull(result);
    }
}

This all builds well, but when the test runs I see the following error:

SetUp : System.NotSupportedException : Unsupported expression: service => service.GetSomeData(IsAny()) at Moq.MatcherFactory.CreateMatcher(Expression expression, Boolean isParams) at Moq.MethodCall..ctor(Mock mock, Condition condition, Expression originalExpression, MethodInfo method, Expression[] arguments) at Moq.MethodCallReturn..ctor(Mock mock, Condition condition, Expression originalExpression, MethodInfo method, Expression[] arguments) at Moq.MethodCallReturn2..ctor(Mock mock, Condition condition, Expression originalExpression, MethodInfo method, Expression[] arguments) at Moq.Mock.<>c__DisplayClass1c2.b__1b() at Moq.PexProtector.Invoke[T](Func1 function) at Moq.Mock.Setup[T,TResult](Mock1 mock, Expression1 expression, Condition condition) at Moq.Mock1.Setup[TResult](Expression`1 expression) at MyProj.Tests.Controllers.SomeControllerTests.Setup() in path location

Any help would be greatly appreciated!

Upvotes: 1

Views: 1310

Answers (1)

JasonWilczak
JasonWilczak

Reputation: 2403

Well, after some work, I think I figured it out. I needed to match the function parameter in my IServiceChannel Implementation:

public TResult Fetch<TResult>(Func<T, TResult> block)
{ ... }

So now my test Setup changed to look like this:

 var someChannelMoc = new Mock<IServiceChannel<ISomeService>>();
            someChannelMoc.Setup(i => i.Fetch(It.IsAny<Func<ISomeService,GetSomeDataResponse>>()))
                .Returns(()=>new GetSomeDataResponse{AllData= new List<Data>()});

            _someChannel = someChannelMoc.Object;

For the sake of something better, I'm going to hold off marking this as answered to see if something better comes along as I cannot specify the input parameter for the function. That isn't a problem for me, but it may be for someone in the future.

Upvotes: 1

Related Questions