我如何在.NET Core中使用ApplicationDbContext

Ray*_*Ray 19 moq asp.net-core

我是第一次尝试使用.NET Core,看看Moq如何用于单元测试.开箱即用,创建控制器,其中ApplicationDbContext是构造函数的参数,如下所示:

public class MoviesController : Controller
{
    private readonly ApplicationDbContext _context;

    public MoviesController(ApplicationDbContext context)
    {
        _context = context;    
    }
Run Code Online (Sandbox Code Playgroud)

这是我在测试控制器时开始的单元测试:

[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));
    }
Run Code Online (Sandbox Code Playgroud)

但后来我意识到ApplicationDbContext是一个具体的类,它没有无参数构造函数,所以测试不起作用.它给了我错误:找不到无参数构造函数.

也许这可能是一个更多针对Moq而不是与.NET Core相关的问题,但我也是Moq的新手,所以我不知道如何继续.以下是创建项目时ApplicationDbContext代码的生成方式:

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; }
}
Run Code Online (Sandbox Code Playgroud)

我需要更改什么才能使我的单元测试成功?

更新:

我从https://msdn.microsoft.com/en-us/magazine/mt703433.aspx发现,您可以将EF Core配置为使用内存数据库进行单元测试.所以我改变了我的单元测试,看起来像这样:

    [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));
    }
Run Code Online (Sandbox Code Playgroud)

此测试现在成功.但这是正确的做法吗?显然,我完全消除了用Moq模拟ApplicationDbContext!或者是否有使用Moq的此问题的另一种解决方案.

Tim*_*riv 20

模拟DbContext不起作用,因为需要太多的提供程序才能使其工作.更简单的解决方案是使用Microsoft为此目的实现的InMemory解决方案.请不要创建仅用于测试的仓库(仍然不测试EF代码).

以下是如何使用.net core中的InMemory数据库进行测试的链接

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


Ger*_*oli 7

您不应该尝试直接模拟数据库上下文.而是实现存储库模式并改为模拟存储库.

这里有一个很好的指导如何正确实现模式.

  • 如果您尚未在应用程序中使用存储库模式,则不应仅为测试目的添加更多层. (21认同)
  • 我将提供与菲利普·科普利不同的观点。如果当前测试很困难并且添加层使测试变得简单,那么这可能是一个非常明显的迹象,表明您的项目“应该”具有该层。测试通过暴露过度耦合来完成其工作。请记住,测试是代码的第一个使用者。如果你不能支持你的第一个消费者,你也不会支持你的第二个消费者。 (2认同)

小智 5

如果有人感兴趣,我会了解@Corporalis的想法,并实现了一个接口以将ApplicationDbContext暴露给测试项目。

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

    int SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

并在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

不要忘记在StartUp.cs,ConfigureServices中注册接口

services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
Run Code Online (Sandbox Code Playgroud)

因此,现在在Controller或Service或您必须使用ApplicationDbContext的每个方法中,改为对接口进行引用

public class filterMovies
{
   private readonly IApplicationDbContext _context

   public filterMovies(IApplicationDbContext context)
   {
     _context = context;
   }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以随意使用接口来模拟ApplicationDbContext,而不是自己实现

 var mockContext = new Mock<IApplicationDbContext>();
 mockContext.Setup(mc => mc.Movies).Returns(mockDbSet.Object);
Run Code Online (Sandbox Code Playgroud)

希望对您有所帮助;)


Ale*_*pov 5

只需 2 行:

var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
var context = new Mock<ApplicationDbContext>(optionsBuilder.Options);
Run Code Online (Sandbox Code Playgroud)