【问题标题】:How to use DbContext inside xUnit Test project?如何在 xUnit 测试项目中使用 DbContext?
【发布时间】:2018-03-23 12:44:26
【问题描述】:

我为我的核心 1.0 应用程序添加了一个测试项目。在其中我有在构造函数中使用 dbContext 的存储库。

如何在测试项目中访问这个 dbContext 并正确注入?

这是我的 ApplicationDbContext 类:

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

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

        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<ShoppingCartItem> ShoppingCartItems { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderDetail> OrderDetails { get; set; }
    }

这是我的单元测试方法:

[Fact]
public void ProductsGetAll()
        {
        //here i need to get acces to dbContext and inject it below

            ProductRepository productRepository = new ProductRepository(dbContext);
            CategoryRepository categoryRepository = new CategoryRepository(dbContext);
            ProductController productController = new ProductController(productRepository, categoryRepository);
        }

更新

我在测试构造函数中添加了上下文:

ApplicationDbContext dbContext;

    public UnitTest1()
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
             .UseInMemoryDatabase(databaseName: "FakeDatabase")
             .Options;

        dbContext = new ApplicationDbContext(options);
    }

和我的测试方法:

    [Fact]
        public void ProductsGetAll()
        {
            IProductRepository productRepository = new ProductRepository(dbContext);
            ICategoryRepository categoryRepository = new CategoryRepository(dbContext);
            ProductController productController = new ProductController(productRepository, categoryRepository);

            var result = productController.List("Pizza");

            var contentResult = result as ViewResult;
            var final = contentResult != null;
            Assert.False(final, "Shouldnt be null");

但现在我有一个错误:

Message: System.NullReferenceException : Object reference not set to an instance of an object.

【问题讨论】:

  • 你到底在测试什么?如果是ProductController,那么您应该模拟存储库以进行单元测试。另一方面,如果您正在进行集成测试,那么您必须指向一个实际的数据库,如果您希望它在本地机器上工作并构建服务器,您可能需要编写代码来生成一个临时数据库利用。另一种方法是在您的配置中保留连接字符串信息以指向本地位置并构建服务器数据库以进行测试。但是,您必须小心更改数据库的测试以及多次运行测试时这意味着什么。
  • 通常您在单元测试中所做的是创建数据库的模拟,因为您不想在单元测试中测试用于集成测试的数据库。如果你模拟你的数据库和实体,你可以简单地使用一个 InitializeTest 方法来初始化你的测试上下文和模拟实体。我建议使用起订量
  • 您是否考虑过使用内存数据库进行测试并在您的测试中使用它注入上下文?见docs.microsoft.com/en-us/ef/core/miscellaneous/testing/…
  • 是否有任何选项可以为每种测试方法仅使用一个上下文?
  • 单元测试不应该处理数据(例如DbContext)。这是一个内部方面,与逻辑无关。应该模拟您的存储库,并且可以使用接口来完成。

标签: c# asp.net unit-testing asp.net-core


【解决方案1】:

我创建了一个interface,并将所有 DbSet 放入其中。

public class ApplicationDbContext : 
   IdentityDbContext<ApplicationUser>,  IDbContext
{
    ...
    public DbSet<Category> Categories { get; set; }
    ...
}

public interface IDbContext
{
   DbSet<Category> Categories { get; set; }
   ...
}

我的repository 依赖于接口而不是 DbContext。

public class UserRepository : IUserRepository
{
   private readonly IDbContext _context;

   public UserRepository(IDbContext context)
   {
      _context = context;
   }
}

然后,我将 IDbContext 模拟为 this,而不是实际的 ApplicationDbContext。

var mockSet = MockHelper.GetMockDbSet(_users);
var mockDbContext = new Mock<IDbContext>();
mockDbContext.Setup(x => x.Users).Returns(mockSet.Object);
varmockDbContext = mockDbContext.Object;

更新

根据问题,您正在尝试模拟存储库。

如果是这样,我建议您创建接口并实现这些接口。然后将这些接口注入构造函数,而不是注入具体的类。例如。使用IProductRepository 而不是ProductRepository。

然后您可以像 this 一样简单地模拟 -

var mockProductRepository = new Mock<IProductRepository>();
mockProductRepository.Setup(x => x.GetAllProductsAsync()).ReturnsAsync(_products);
var productController = new ProductController(mockProductRepository.Object,...);

【讨论】:

  • 这当然可以,但我认为他应该改为模拟存储库。
  • @JuanR 不错;我没有注意到 OP 正在尝试模拟存储库。我更新了答案。
【解决方案2】:

我不是 .NET Core 人员,但我想您可以尝试使用内存数据库进行单元测试。

在旧版本的 EntityFramework 中,您可以使用 Effort,EF Core 似乎已经解决了这个 oob。我不确定这是否会立即奏效,因为我自己从未尝试过(同样,不是 .net 核心人员),但您应该这样开始:

首先,添加包Microsoft.EntityFrameworkCore.InMemory。
然后创建上下文并将其配置为使用 In-Memory-DB

var options = new DbContextOptionsBuilder<ApplicationDbContext>()
    .UseInMemoryDatabase(databaseName: "pick_or_generate_a_unique_name_here")
    .Options;

var context = new ApplicationDbContext(options);

最后,将上下文注入您的存储库并在您的测试中使用它们

var productRepository = new ProductRepository(context);
var categoryRepository = new CategoryRepository(context);
var productController = new ProductController(productRepository, categoryRepository);

请参阅此blogpost 和MSDN 了解更多信息。

【讨论】:

  • 是否有任何选项可以为每种测试方法仅使用一个上下文?
  • 我不知道 XUnit,但您可以在某种设置方法中使用不同的名称简单地创建一个新的(在 xunit 中这似乎是 ctor)
  • @ChrisK.:在 xUnit 中,设置是通过构造函数处理的,因此在此处设置上下文并将其保存到私有字段。
  • Microsoft 现在不鼓励使用 EntityFramework 数据库。您可以改用 SQLite 内存数据库 (docs.microsoft.com/en-us/ef/core/testing/…)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-03
  • 2021-11-04
  • 2019-05-26
  • 1970-01-01
相关资源
最近更新 更多