22
votes

I'm trying out .NET Core for the first time and seeing how Moq can be used in unit testing. Out of the box, the controllers are created where the ApplicationDbContext are parameters to the constructor like this:

public class MoviesController : Controller
{
    private readonly ApplicationDbContext _context;

    public MoviesController(ApplicationDbContext context)
    {
        _context = context;    
    }

Here is the unit test that I started with when testing the controller:

[TestClass]
public class MvcMoviesControllerTests
{
    [TestMethod]
    public async Task MoviesControllerIndex()
    {
        var mockContext = new Mock<ApplicationDbContext>();            
        var controller = new MoviesController(mockContext.Object);

        // Act
        var result = await controller.Index();

        // Assert
        Assert.IsInstanceOfType(result, typeof(ViewResult));
    }

But then I realized ApplicationDbContext is a concrete class AND it does not have a parameterless constructor so the test won't work. It gives me error: Could not find parameterless constructor.

Perhaps this may be a question more aimed at Moq rather than it being related to .NET Core, but I'm also new to Moq so I'm not sure how to proceed. Here is how the ApplicationDbContext code was generated when I created the project:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }

    public DbSet<Movie> Movie { get; set; }
}

What do I need to change so that my unit test would succeed?

UPDATE:

I discovered from https://msdn.microsoft.com/en-us/magazine/mt703433.aspx that you can configure EF Core to use an in-memory database for unit testing. So I changed my unit test to look like this:

    [TestMethod]
    public async Task MoviesControllerIndex()
    {        
        var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
        optionsBuilder.UseInMemoryDatabase();
        var _dbContext = new ApplicationDbContext(optionsBuilder.Options);

        var controller = new MoviesController(_dbContext);

        // Act
        var result = await controller.Index();

        // Assert
        Assert.IsInstanceOfType(result, typeof(ViewResult));
    }

This test now succeeds. But is this the proper way of doing this? Obviously, I completely eliminated mocking the ApplicationDbContext with Moq! Or is there another solution to this problem using Moq.

5
Even with in-memory DbContext you are basically doing integration testing which is still necessary. You however, from a design perspective, should have your classes depend on abstractions and not concretions. Create an interface that exposes the functionality you need and have your concrete context inherit from that. - Nkosi
I agree with @Nkosi this is now effectively an integration test. Surely as ApplicationDbContext implements ` IdentityDbContext<ApplicationUser>`, could you possibly inject the interface into your controller and then mock that instead? - Corporalis

5 Answers

25
votes

Mocking the DbContext doesn't work because there are too many providers that are required to make it work. A much easier solution is to use the InMemory solution that Microsoft has implemented for this exact purpose. Please don't create a repo just to test(which still doesn't test the EF code).

Here is a link to how to test with InMemory databases in .net core

https://docs.efproject.net/en/latest/miscellaneous/testing.html

9
votes

If someone have interest, I got part of the idea of @Corporalis and implemented a interface to expose ApplicationDbContext to the test project.

public interface IApplicationDbContext
{
    DbSet<Movie> Movies { get; set; }

    int SaveChanges();
}

And in ApplicationDbContext

 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IApplicationDbContext
{
    public virtual DbSet<Movie> Movies { get; set; }

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}

Don“t forget to register the Interface in StartUp.cs, ConfigureServices

services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());

So, now in the Controller or Service or every Method that you have to use ApplicationDbContext, do it a reference to the Interface instead

public class filterMovies
{
   private readonly IApplicationDbContext _context

   public filterMovies(IApplicationDbContext context)
   {
     _context = context;
   }
}

Now we'll feel free to mock the ApplicationDbContext using the interface instead the implementation it self

 var mockContext = new Mock<IApplicationDbContext>();
 mockContext.Setup(mc => mc.Movies).Returns(mockDbSet.Object);

I hope it helps ;)

6
votes

You shouldn't try to mock the database context directly. Instead implement the Repository Pattern and mock the repository instead.

Here there is a great guide on how to implement the pattern correctly.

3
votes

I have created a library that allows mocking EF Core instead of using the in-memory database, see Github. (Ported from EF6 version)

There are integration NuGet packages for use with Moq or NSubstitute.

Example usage

public class User
{
    [Key, Column(Order = 0)]
    public Guid Id { get; set; }
    public string FullName { get; set; }
}

public class TestDbContext : DbContext
{
    public TestDbContext(DbContextOptions<TestDbContext> options)
        : base(options) {}

    public virtual DbSet<User> Users { get; set; }
}

[TestFixture]
public class MyTests
{
    var initialEntities = new[]
        {
            new User { Id = Guid.NewGuid(), FullName = "Eric Cartoon" },
            new User { Id = Guid.NewGuid(), FullName = "Billy Jewel" },
        };

    var dbContextMock = new DbContextMock<TestDbContext>(DummyOptions);
    var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Users, initialEntities);

    // Pass dbContextMock.Object to the class/method you want to test

    // Query dbContextMock.Object.Users to see if certain users were added or removed
    // or use Mock Verify functionality to verify if certain methods were called: usersDbSetMock.Verify(x => x.Add(...), Times.Once);
}

public DbContextOptions DummyOptions { get; } = new DbContextOptionsBuilder().Options;
0
votes

Just 2 lines:

var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
var context = new Mock<ApplicationDbContext>(optionsBuilder.Options);