0

I'm trying to use Moq (4.10) on async calls but I cannot get the hang of it.

Search on how to do so and found answers which I've tried but I cannot make it to work .

This is my test

 public class Test
 {
        [Fact]
        public void Test_Create()
        {
            var repositoryMock = new Mock<IRepository>();

            repositoryMock
             .Setup(repo => repo.CreateAsync(It.IsAny<Aggregate >()))
             .Returns(Task.CompletedTask); 
/// also tried this 
/// => .Returns(Task.FromResult(default(object))) 
/// and 
/// => .Returns(Task.FromResult(false))); 

            var useCase = new CreateUseCase(repositoryMock.Object);

            Task.Run(async () => { await useCase.HandleAsync(new CreateRequest()); });

            repositoryMock.VerifyAll();
        }
}

resources

How can I tell Moq to return a Task?

Getting this exception

Moq.MockException: 'The following setups on mock 'Mock<.Repository.IRepository:00000001>' were not matched: IRepository repo => repo.CreateAsync(It.IsAny < Aggregate>())

The repo looks like this

public interface IRepository 
{
    Task CreateAsync(Aggregate aggregate);
}

The UseCase

    public class CreateUseCase : IUseCaseHandler<CreatRequest>
    {

        private IRepository _repository;


        public CreateUseCase (IRepository repository)
        {
            _repository= repository?? throw new System.ArgumentNullException(nameof(repository));
        }


         public async Task HandleAsync(CreateRequest request, CancellationToken? cancellationToken = null)
         {
              Aggregate aggregate = new Aggregate();
                        aggregate.Create();
              await _repository.CreateAsync(aggregate);
          } 
     }

The Repository

 public sealed class OrderRepository : ProxyRepository<OrderAggregate>, IOrderRepository
    {
        public OrderRepository(IUnitOfWork unitOfWork, INotificationDispatcher eventHandler) 
            : base(unitOfWork, eventHandler)
        {
        }

        async Task IRepository.CreateAsync(Aggregate aggregate)
        {
            await base.AddAsync(aggregate); 
        }
    }

What is it that I'm doing wrong or missing ?

2 Answers2

0

We normally do not have to mock a method of an interface which doesn't return any unless I'm missing something in your question.

public class CreateUseCaseTests
{
    [Fact]
    public async Task HandleAsync_ShouldCreateRequest()
    {
        // Arrange
        var repositoryMock = new Mock<IRepository>();    
        var sut = new CreateUseCase(repositoryMock.Object);

        // Act
        await sut.HandleAsync(new CreateRequest());

        // Assert
        repositoryMock.Verify(x => x.CreateAsync(It.IsAny<Aggregate>()), Times.Once);
    }
}
Win
  • 61,100
  • 13
  • 102
  • 181
  • Is that why he's getting the exception? – Robert Harvey Nov 06 '18 at 16:02
  • Moq with some versions of Castle has an issue. OP's following code works in `Moq 4.10.0` and `Castle.Core 4.3.1`. `repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).Returns(Task.CompletedTask).Verifiable();` – Win Nov 06 '18 at 16:25
  • @Win , I've tried both versions. The one in the main thread doesn't get CreateAsync invoked. The 2nd I've replaced with repositoryMock.Setup(repo => repo.CreateOrderAsync(It.IsAny())).Returns(Task.CompletedTask).Verifiable(); the Arrange .... and with repositoryMock.VerifyAll(); the Assert . getting same exception –  Nov 06 '18 at 16:40
  • @MCR I could not say what version of `Moq` and `Castle` have the issue. The main reason you want to setup a method returned `Task` is to verify the call. So, you could explicitly verify like in my answer. – Win Nov 06 '18 at 16:46
0

I don't think your problem is with the Moq setup at all. The problem is that the unit test runs the meaningful code using Task.Run(), which spawns off a new thread. Then back on the original thread, you immediately test whether the Moq setup has been fulfilled. Since the code under test is launched on a different thread, there is a very real chance that the test for success comes before the code under test is executed.

You should change your unit test to run the test method using async & await, rather than spinning off a new thread. Note that the signature of the test case changes from void to async Task, and we await the code we're testing.

public class Test
{
    [Fact]
    public async Task Test_Create()
    {
        var repositoryMock = new Mock<IRepository>();

        repositoryMock
             .Setup(repo => repo.CreateAsync(It.IsAny<Aggregate >()))
             .Returns(Task.CompletedTask); 

        var useCase = new CreateUseCase(repositoryMock.Object);

        await useCase.HandleAsync(new CreateRequest());

        repositoryMock.VerifyAll();
    }
}
Matt Dillard
  • 14,677
  • 7
  • 51
  • 61