Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 891
Jason Hao Avatar asked Mar 07 '18 01:03

Jason Hao


4 Answers

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.

like image 93
Nkosi Avatar answered Nov 17 '22 05:11

Nkosi


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

.Returns((T model, CancellationToken token) => new ValueTask<EntityEntry<T>>());
like image 23
user007 Avatar answered Nov 17 '22 04:11

user007


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)
)));
like image 2
Ivan Gligorijevic Avatar answered Nov 17 '22 04:11

Ivan Gligorijevic


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();
}
like image 1
Shuvo Amin Avatar answered Nov 17 '22 04:11

Shuvo Amin