'How do I mock AddAsync?

I'm writing unit test. For testing the method below,

public async Task<Guid> CreateWebJobStatus(string blobId, Guid loggedInUserId, string loggedInUserEmail) {

    Guid webJobStatusId = Guid.NewGuid();

    WebJobStatus newWebJobStatus = new WebJobStatus
    {
        WorkJobStatusId = webJobStatusId,
        TransactionId = Guid.NewGuid(),
        Status = (int)WebJobStatusEnum.PENDING,
        BlobId = blobId,
        UserId = loggedInUserId,
        UserEmail = loggedInUserEmail,
    };
    await _dbContext.WebJobStatus.AddAsync(newWebJobStatus);
    await _dbContext.SaveChangesAsync();

    return webJobStatusId;
}

I mocked dbset and dbcontext

public async void CreateWebJobStatusTest() {
    var dbOption = new DbContextOptions<TimeSeriesDbContext>();
    var mockDbContext = new Mock<TimeSeriesDbContext>(dbOption);
    var mockConfig = new Mock<IConfiguration>();
    var instance = new WebJobStatusRepository(mockConfig.Object, mockDbContext.Object);
    var mockValidWebJobId = "11111111-1111-1111-1111-111111111111";
    var webjobStatusList = new List<WebJobStatus>() {
        new WebJobStatus {
            WorkJobStatusId = Guid.Parse(mockValidWebJobId),
            GroupName = "testGroupName",
            Status = 3,
            CreatedDate = DateTimeOffset.UtcNow.AddDays(-10)
        }
    };
    var mockWebJobDbSet = UnitTestUtil.CreateDbSetMock<WebJobStatus>(webjobStatusList.AsQueryable());
    mockDbContext.Setup(x => x.WebJobStatus).Returns(mockWebJobDbSet.Object);

    mockWebJobDbSet.Setup(x => x.AddAsync(It.IsAny<WebJobStatus>(), It.IsAny<System.Threading.CancellationToken>())).Callback((WebJobStatus wj) =>{webjobstatusList.add(wj);});


    var mockuserId = Guid.Parse("22222222-1111-1111-1111-111111111111");

    var result = await instance.CreateWebJobStatus("testBlobId.tsv", mockuserId, "testEmail");
    Assert.IsType<Guid>(result);
    mockDbContext.Verify(x => x.SaveChangesAsync(It.IsAny<System.Threading.CancellationToken>()), Times.Once);
    mockWebJobDbSet.Verify(x => x.AddAsync(It.IsAny<WebJobStatus>(), It.IsAny<System.Threading.CancellationToken>()), Times.Once);
}

everything works as expect except the AddAsync, the exception is

Invalid callback. Setup on method with parameters (WebJobStatus,CancellationToken) cannot invoke callback with parameters (WebJobStatus).

Anyone has any idea?



Solution 1:[1]

You will need to return a Task to allow the async/await call

await _dbContext.WebJobStatus.AddAsync(newWebJobStatus);

to flow to completion.

So assuming that Add returns the object added

mockWebJobDbSet
    .Setup(_ => _.AddAsync(It.IsAny<WebJobStatus>(), It.IsAny<System.Threading.CancellationToken>()))
    .Callback((WebJobStatus model, CancellationToken token) => { webjobstatusList.Add(model); })
    .Returns((WebJobStatus model, CancellationToken token) => Task.FromResult((EntityEntry<WebJobStatus>)null));

Note that the method being Setup takes two arguments, so the Callback and Returns will need to expect two arguments as well if they want to use the captured arguments.

Solution 2:[2]

In .net core 3.1, I had to rewrite the Returns as below:

.Returns((T model, CancellationToken token) => new ValueTask<EntityEntry<T>>());

Solution 3:[3]

Not pretty, but this was the only way I found to mock AddAsync and make it return an actual value:

mockWebJobDbSet
.Setup(_ => _.AddAsync(It.IsAny<WebJobStatus>(), It.IsAny<CancellationToken>()))
.Callback((WebJobStatus model, CancellationToken token) => { webjobStatusList.Add(model); })
.Returns((WebJobStatus model, CancellationToken token) => ValueTask.FromResult(new EntityEntry<WebJobStatus>
(new InternalEntityEntry(
    new Mock<IStateManager>().Object,
    new RuntimeEntityType("WebJobStatus", typeof(WebJobStatus), false, null, null, null, ChangeTrackingStrategy.Snapshot, null, false),
    model)
)));

Solution 4:[4]

The way I have done mocking is the following:-

var myList = new List<MyClass>();
       
_dataContext.Setup(m => m.MyClasses.AddAsync(It.IsAny<MyClass>(), default))
            .Callback<Slide, CancellationToken>((s, token) => {myList.Add(s);});
        
_dataContext.Setup(c => c.SaveChangesAsync(default))
            .Returns(Task.FromResult(1))
            .Verifiable();

And the actual implementation is the following:-

public async Task<int> AddMyClassAsync(MyClass myClass)
{
    await _careMapsDataContext.MyClasses.AddAsync(myClass);
        
    return await _careMapsDataContext.SaveChangesAsync();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 user007
Solution 3 Ivan Gligorijevic
Solution 4 Shuvo Amin