【问题标题】:How do I Moq the ApplicationDbContext in .NET Core如何在 .NET Core 中起订 ApplicationDbContext
【发布时间】:2021-07-11 19:09:15
【问题描述】:

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

public class MoviesController : Controller
{
    private readonly ApplicationDbContext _context;

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

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

[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));
    }

但后来我意识到 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; }
}

我需要进行哪些更改才能使我的单元测试成功?

更新:

我从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));
    }

这个测试现在成功了。但这是正确的做法吗?显然,我完全消除了使用 Moq 模拟 ApplicationDbContext!或者是否有其他使用 Moq 来解决这个问题的方法。

【问题讨论】:

  • 即使使用内存中的 DbContext,您基本上也是在进行集成测试,这仍然是必要的。然而,从设计的角度来看,你应该让你的类依赖于抽象而不是具体化。创建一个接口来公开您需要的功能并让您的具体上下文继承自该接口。
  • 我同意@Nkosi 这现在实际上是一个集成测试。当然ApplicationDbContext 实现了`IdentityDbContext`,你可以将接口注入你的控制器然后模拟它吗?

标签: asp.net-core moq


【解决方案1】:

模拟 DbContext 不起作用,因为需要太多提供程序才能使其工作。一个更简单的解决方案是使用 Microsoft 为此目的实施的 InMemory 解决方案。请不要创建一个 repo 只是为了测试(它仍然不测试 EF 代码)。

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

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

【讨论】:

    【解决方案2】:

    如果有人有兴趣,我得到了@Corporalis 的部分想法,并实现了一个接口来将 ApplicationDbContext 暴露给测试项目。

    public interface IApplicationDbContext
    {
        DbSet<Movie> Movies { get; set; }
    
        int SaveChanges();
    }
    

    在 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);
        }
    }
    

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

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

    所以,现在在必须使用 ApplicationDbContext 的控制器或服务或每个方法中,改为引用接口

    public class filterMovies
    {
       private readonly IApplicationDbContext _context
    
       public filterMovies(IApplicationDbContext context)
       {
         _context = context;
       }
    }
    

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

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

    希望对你有帮助;)

    【讨论】:

    【解决方案3】:

    您不应该尝试直接模拟数据库上下文。而是实现 Repository Pattern 并模拟存储库。

    这里有一个great guide关于如何正确实现该模式。

    【讨论】:

    • 如果您尚未在应用程序中使用存储库模式,则不应仅出于测试目的添加更多层。
    • 我将提供与 Phillip Copley 不同的观点。如果当前测试很困难并且添加一个层使测试变得简单,那么这可能是一个非常明确的信号,表明您的项目应该具有该层。测试通过暴露过度耦合来完成它的工作。请记住,测试是代码的第一个消费者。如果你不能支持你的第一个消费者,你也不会支持你的第二个消费者。
    • 抱歉,我在发布此答案时没有考虑重构时间...我已经在使用该模式,所以我有偏见。在许多情况下,InMemory 更合适。
    【解决方案4】:

    我创建了一个允许模拟 EF Core 而不是使用内存数据库的库,请参阅Github。 (转自EF6 version

    有用于MoqNSubstitute 的集成 NuGet 包。

    使用示例

    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;
    

    【讨论】:

      【解决方案5】:

      只有两行:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-29
        • 1970-01-01
        • 2019-05-28
        • 2018-12-21
        • 2019-10-13
        • 1970-01-01
        • 2021-10-22
        相关资源
        最近更新 更多