简体   繁体   中英

Mocked async method never called in Moq

I have a database repository interface:

public interface IDbRepository
{
  Task<T> GetDataAsync<T>(Func<T, bool> filter = null) where T : class;
}

I have the following fixture:

private List<Status> statusList = new List<Status> {
      new Status { Name="Status A"},
      new Status { Name="Status B"}
    };

var repositoryMock = new Mock<IDbRepository>();
Func<Status, bool> filter = It.IsAny<Func<Status, bool>>();
repositoryMock.Setup(repository => repository.GetDataAsync(filter))
  .ReturnsAsync(
  () =>
  {
    //NEVER CALLED
    if (filter == null)
      return statusList.FirstOrDefault();
    return statusList.FirstOrDefault(filter);
  });

I use the following test:

[Fact]
public async Task Repository_GetDataAsync_Filter()
{
  var repository = repositoryTestFixture.Ioc.Resolve<IDbRepository>();
  Func<Status, bool> filter = stat => stat.Name == "Status A";
  var res = await repository.GetDataAsync(filter);
  //await task1.ContinueWith(ctask => Assert.NotNull(ctask.Result));
  Assert.NotNull(res); //ALWAYS NULL!!!
}

I tried to debug, and the method never called so I always get a failed test.

According to @Stephen Cleary, the test isn't required to call Wait() or Result , and the old official way is deprecated as of Visual Studio 2015, async tests are supported out of the box.

Stephen Cleary • https://stephencleary.com/ 11 months ago (2017-11-03 12:22:06 pm) As noted in the big red banner at the top of the page, this solution was designed for VS2010. VS2015 works fine with async Task unit tests out of the box.

What should I change?

The use of the It.IsAny<>() is inaccurate. It was meant to be used directly in the Setup expression only. Not in variables.

When arguments do not match the mock is not invoked. Hence you get null.

Access passed argument within the ReturnAsync delegate

var repositoryMock = new Mock<IDbRepository>();
repositoryMock
    .Setup(repository => repository.GetDataAsync(It.IsAny<Func<Status, bool>>()))
    .ReturnsAsync((Func<Status, bool> filter) => {//<-- grab passed argument here
        if (filter == null)
            return statusList.FirstOrDefault();
        return statusList.FirstOrDefault(filter);
    });

I think the problem here is due to your filter local variable. By creating a variable that is not an expression, you are actually executing the It.IsAny call and returning null. As you can imagine, null will only match a null func, and this is why you don't see the ReturnsAsync being hit.

Please try inlining that variable or changing its type to Expression<Func<Status, bool>> and see if that works as expected.

You have to always keep in mind that mock setup is done using expressions, so usually the It.IsX calls are never executed per se, just analysed and compared as an expression tree.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM