18
Mock<IDbContext> dbContext;

[TestFixtureSetUp]
public void SetupDbContext()
{
    dbContext = new Mock<IDbContext>();
    dbContext.Setup(c => c.SaveChanges()).Verifiable();
    dbContext.Setup(c => c.SaveChangesAsync()).Verifiable();
    dbContext.Setup(c => c.Customers.Add(It.IsAny<Customer>()))
             .Returns(It.IsAny<Customer>()).Verifiable();
}

[Test]
public async Task AddCustomerAsync()
{
    //Arrange
    var repository = new EntityFrameworkRepository(dbContext.Object);
    var customer = new Customer() { FirstName = "Larry", LastName = "Hughes" };

    //Act
    await repository.AddCustomerAsync(customer);

    //Assert
    dbContext.Verify(c => c.Customers.Add(It.IsAny<Customer>()));
    dbContext.Verify(c => c.SaveChangesAsync());
}

[Test]
public void AddCustomer()
{
    //Arrange
    var repository = new EntityFrameworkRepository(dbContext.Object);
    var customer = new Customer() { FirstName = "Larry", LastName = "Hughes" };

    //Act
    repository.AddCustomer(customer);

    //Assert
    dbContext.Verify(c => c.Customers.Add(It.IsAny<Customer>()));
    dbContext.Verify(c => c.SaveChanges());
}

And here's what I want to test:

public class EntityFrameworkRepository
{
    private readonly IDbContext DBContext;

    public EntityFrameworkRepository(IDbContext context)
    {
        DBContext = context;
    }

    public async Task AddCustomerAsync(Customer customer)
    {
        DBContext.Customers.Add(customer);
        await DBContext.SaveChangesAsync();
    }

    public void AddCustomer(Customer customer)
    {
        DBContext.Customers.Add(customer);
        DBContext.SaveChanges();
    }
}

AddCustomers test passes.

AddCustomersAsync test fails, I keep getting a NullReferenceException after calling await DbContext.SaveChangesAsync().

at MasonOgCRM.DataAccess.EF.EntityFrameworkRepository.d__2.MoveNext() in C:\Users\Mason\Desktop\Repositories\masonogcrm\src\DataAccess.EFRepository\EntityFrameworkRepository.cs:line 43

I can't see anything that's null in my code. DbContext is not null. The equivalent test of AddCustomers which is identical with the exception of not being async runs as expected. I suspect I haven't performed a correct setup of SaveChangesAsync in SetupDBContext() but I don't know what to do to fix it.

Old Fox
  • 8,629
  • 4
  • 34
  • 52
mason
  • 31,774
  • 10
  • 77
  • 121

4 Answers4

12

You could use

dataContext.Setup(x => x.SaveChangesAsync()).ReturnsAsync(1);

and

dataContext.Verify(x=>x.SaveChangesAsync());
  • 7
    I'm using .Net Core 2.2 SDK, didn't work with out the CancellationToken parameter. mockMessageContext.Setup(m => m.SaveChangesAsync(It.IsAny())).ReturnsAsync(5); – Sudherson Vetrichelvan Jan 23 '19 at 14:39
  • am using .netcore 3.1 and agree that it no requires cancellation token. but my verify calls are failing. mockDb.Verify(a => a.SaveChangesAsync(It.IsAny()),Times.Exactly(2)); Expected invocation on the mock at least once, but was never performed: a => a.SaveChangesAsync(It.IsAny()) – krishnakumar Jun 25 '20 at 23:16
  • .net 5 also requires It.IsAny() to verify SaveChangesAsync. – Tegge Oct 21 '21 at 06:12
11

You are right the problem occurs because one of your setups incorrect :: dbContext.Setup(c => c.SaveChangesAsync()).Verifiable();.

The method return a Task and you forgot to return a Task therefore null returns.

You can remove dbContext.Setup(c => c.SaveChangesAsync()).Verifiable(); or change the setup to something like:

dbContext.Setup(c => c.SaveChangesAsync()).Returns(() => Task.Run(() =>{})).Verifiable();
Old Fox
  • 8,629
  • 4
  • 34
  • 52
  • Got it. `SaveChangesAsync` actually returns a `Task` so I modified the setup to this: `dbContext.Setup(c => c.SaveChangesAsync()).Returns(() => Task.Run(() => { return 1; })).Verifiable();` and that did the trick! – mason Sep 26 '15 at 12:58
  • @mason you welcome :-) In my opinion I'd remove this setup, It doesn't give you anything unless you use `VerifyAll` – Old Fox Sep 26 '15 at 15:55
1

This worked for me:

dbContext.Verify(m => m.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once());
Adrita Sharma
  • 21,581
  • 10
  • 69
  • 79
0

It may also require a cancellation token, so something like this should work:

dataContext.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);