首页 > 解决方案 > 实体框架模拟需要全局上下文

问题描述

我最近开始深入研究使用 Entity Framework 6 模拟的 Entity Framework 单元测试。

我注意到以下事情:

实体框架模拟迫使我在我的 BL 类中创建一个全局上下文,例如:

public class RefundRepayment : IDisposable
{
    protected DbContext _dbContext = new DbContext();

    /* more properties and class code */

    public void Dispose()
    {
        _dbContext.Dispose();
    }
}

我不太明白,因为我宁愿using在每个方法中实现语句以处理DbContext,我的代码将如下所示:

public class RefundRepayment
{
    /* more properties and class code */
    public void AccessDb() 
    {
        using(DbContext dbContext = new DbContext())
        {
            /* db code here */
        }
    }
}

为什么我们应该初始化全局上下文而不是执行using语句,有什么具体原因吗?

标签: c#entity-frameworkunit-testingentity-framework-6dbcontext

解决方案


首先,您需要使用 DI(通过 ninject、Unity、Core 等)来实现这一目标。

让我向您展示一个简单的 EF GetAll() 示例来测试我的 MVC 控制器。

[Fact]
public void GetAllOk()
{
    // Arrange

    // Act
    var result = _controller.GetAll() as OkObjectResult;

    // Assert
    Assert.NotNull(result);
    var recordList = result.Value as List<DTO.Account>;
    Assert.NotNull(recordList);
    Assert.Equal(4, recordList.Count);
}

它依赖于这个启动代码......

public class AccountsControllerTests
{
    DatabaseFixture _fixture;
    AccountsControllerV1 _controller;

    public AccountsControllerTests(DatabaseFixture fixture)
    {
        _fixture = fixture;
        _controller = new AccountsControllerV1(_fixture._uow);
    }

什么是数据库夹具?很高兴你问...

public class DatabaseFixture : IDisposable
{
    public ApplicationDbContext _context;
    public DbContextOptions<ApplicationDbContext> _options;
    public IUoW _uow;

    public DatabaseFixture()
    {
        var x = Directory.GetCurrentDirectory();
        var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.Tests.json", optional : true)
            .Build();

        _options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(databaseName: "ProviderTests")
            .Options;

        _context = new ApplicationDbContext(_options);
        _context.Database.EnsureCreated();
        Initialize();

        _uow = new UoW(_context);
    }

    private void Initialize()
    {
        _context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 1", AccountID = "", AccountUniqueID = "" });
        _context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 2", AccountID = "", AccountUniqueID = "" });
        _context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 3", AccountID = "", AccountUniqueID = "" });
        _context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 4", AccountID = "", AccountUniqueID = "" });
        _context.SaveChanges();
    }

    public void Dispose()
    {
        // Clean Up
        _context.Database.EnsureDeleted();
    }
}

[CollectionDefinition("Database Collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
}

上面代码中使用的一些定义。我使用了一个包含对我所有 EF 存储库的引用的工作单元模式。我将实体(数据库)类和 DTO(数据传输对象)类分开。我对在每次运行和/或测试开始时初始化的 EF 数据库使用了内存替换,以便我的数据始终是已知的。我将数据库装置注入我的测试类(不是每个测试),所以我不会不断地创建/销毁。然后我创建我的控制器,传入我的数据库 UoW 定义。

您是真正的控制器,需要注入您使用真实数据库创建的 UoW 容器。您只是用一个受控的数据库环境来代替您的测试。

public AccountsControllerV1(IUoW uow)
{
    _uow = uow;
}

是的,我对眼尖的人使用版本控制。是的,这是一个 Core 2 示例。仍然适用于 EF 6,只需要 3rd 方 DI ;)

我正在测试的控制器方法是什么?

[HttpGet("accounts", Name ="GetAccounts")]
public IActionResult GetAll()
{
    try
    {
        var recordList = _uow.Accounts.GetAll();

        List<DTO.Account> results = new List<DTO.Account>();
        if (recordList != null)
        {
            results = recordList.Select(r => Map(r)).ToList();
        }

        log.Info($"Providers: GetAccounts: Success: {results.Count} records returned");
        return Ok(results);
    }
    catch (Exception ex)
    {
        log.Error($"Providers: GetAccounts: Failed: {ex.Message}");
        return BadRequest($"Providers: GetAccounts: Failed: {ex.Message}");
    }
}

推荐阅读