Entity Framework 模拟需要全局上下文
Entity Framework mocking requires global context
我最近开始使用 Entity Framework 6 模拟深入研究 Entity Framework 单元测试。
我注意到以下事情:
Entity Framework 模拟迫使我在我的 BL class 中创建一个全局上下文,例如:
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
语句,有什么具体原因吗?
首先,您需要使用 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);
}
什么是 DatabaseFixture?很高兴你问...
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 存储库的引用。我将实体(数据库)classes 和 DTO(数据传输对象)类 分开。我使用内存中替换我在每个 运行 and/or 测试开始时初始化的 EF 数据库,以便我的数据始终 已知 。我将数据库夹具注入到我的测试 class(不是每个测试)中,所以我不会经常 creating/destroying。然后我创建我的控制器,传入我的数据库 UoW 定义。
你是 real 控制器需要注入你用 real 数据库创建的 UoW 容器。您只是为您的测试替换了一个受控的数据库环境。
public AccountsControllerV1(IUoW uow)
{
_uow = uow;
}
是的,我为眼尖的人使用版本控制。是的,这是一个 Core 2 示例。仍然适用于 EF 6,只需要第 3 方 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}");
}
}
我最近开始使用 Entity Framework 6 模拟深入研究 Entity Framework 单元测试。
我注意到以下事情:
Entity Framework 模拟迫使我在我的 BL class 中创建一个全局上下文,例如:
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
语句,有什么具体原因吗?
首先,您需要使用 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);
}
什么是 DatabaseFixture?很高兴你问...
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 存储库的引用。我将实体(数据库)classes 和 DTO(数据传输对象)类 分开。我使用内存中替换我在每个 运行 and/or 测试开始时初始化的 EF 数据库,以便我的数据始终 已知 。我将数据库夹具注入到我的测试 class(不是每个测试)中,所以我不会经常 creating/destroying。然后我创建我的控制器,传入我的数据库 UoW 定义。
你是 real 控制器需要注入你用 real 数据库创建的 UoW 容器。您只是为您的测试替换了一个受控的数据库环境。
public AccountsControllerV1(IUoW uow)
{
_uow = uow;
}
是的,我为眼尖的人使用版本控制。是的,这是一个 Core 2 示例。仍然适用于 EF 6,只需要第 3 方 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}");
}
}