mb1231
mb1231

Reputation: 376

How to mock delegates correctly

I have an interface and implementation like so:

public interface IScopedServices
{
   Task<TReturn> ExecuteAsync<TService, TReturn>(Func<TService, Task<TReturn>> action);
}

//Implemented like so:

public async Task<TReturn> ExecuteAsync<TService, TReturn>(Func<TService, Task<TReturn>> action)
{
   using (var serviceScope = serviceScopeFactory.CreateScope())
   {
      var service = serviceScope.ServiceProvider.GetService<TService>();
      return await action.Invoke(service);
   }
}

This service wraps other service calls so they can get their own scope, an example of such a service call is:

public class TicketService : ITicketService
{
   public async Task<Ticket> GetTicket(string ticketNumber)
   {
      return await repository.Get(t => t.TicketNumber == ticketNumber);
   }
}

And this is called in my code under test like so:

var ticket = await ScopedServices.ExecuteAsync<ITicketService, Ticket>(s => s.GetTicket("ticket number"));

So in my unit test, I mock it like this, and it works fine, no problem:

var mockScopedServices = new Mock<IScopedServices>();

mockScopedServices.Setup(mss => mss.ExecuteAsync(It.IsAny<Func<ITicketService, Task<Ticket>>>())).ReturnsAsync(TestTicket);

My question is: how can I mock the ITicketService so that when GetTicket it is called with a specific parameter, I return a separate object?

Right now, as I'm new to Moq, I am not sure how to mock the ITicketService so that I have control of what GetTicket takes as an argument and therefore what it returns in such a case.

Upvotes: 4

Views: 1033

Answers (1)

Nkosi
Nkosi

Reputation: 247058

You can use Callback to capture the passed in delegate and then invoke it.

This will, to an extent, be mocking the internal workings of the implementation.

But the same can actually be done within the Returns delegate

var scopedMock = new Mock<ITicketService>();
scopedMock 
    .Setup(_ => _.GetTicket(It.IsAny<string>())) // <-- setup and adjust as needed
    .ReturnsAsync(TestTicket);

var mockScopedServices = new Mock<IScopedServices>();
mockScopedServices
    .Setup(_ => _.ExecuteAsync(It.IsAny<Func<ITicketService, Task<Ticket>>>()))
    .Returns((Func<ITicketService, Task<Ticket>> arg) => {
        //this invokes the passed in delegate using the mocked scoped service
        Task<Ticket> task = arg(scopedMock.Object);
        return task;
    });

Upvotes: 1

Related Questions