问题描述
我最近开始研究使用Entity Framework 6模拟进行的Entity Framework单元测试.
I have recently began to dig into Entity Framework unit-testing with Entity Framework 6 mocking.
我注意到以下情况:
实体框架模拟迫使我在BL类中创建全局上下文,例如:
Entity Framework mocking forces me to create a global context in my BL class, for example:
public class RefundRepayment : IDisposable
{
protected DbContext _dbContext = new DbContext();
/* more properties and class code */
public void Dispose()
{
_dbContext.Dispose();
}
}
我不太清楚,因为我宁愿在每个方法中实现using
语句以处理DbContext
,我的代码如下所示:
I can't quite figure it out, as I'd rather implement the using
statement in every method in order to deal with the DbContext
, my code will look like:
public class RefundRepayment
{
/* more properties and class code */
public void AccessDb()
{
using(DbContext dbContext = new DbContext())
{
/* db code here */
}
}
}
有什么特殊的原因为什么我们应该初始化全局上下文而不是执行using
语句?
Is there any specific reason why should we initialize a global context instead of implementing the using
statement?
推荐答案
首先,您需要使用DI(通过ninject,Unity,Core等)将其实现.
First off, you need to be using DI (via ninject, Unity, Core, etc) to pull this off.
让我向您展示一个测试我的MVC控制器的EF GetAll()的简单示例.
Let me show you a simple sample of an EF GetAll() testing my MVC controller.
[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);
}
它依赖于此启动代码...
It relies on this startup code...
public class AccountsControllerTests
{
DatabaseFixture _fixture;
AccountsControllerV1 _controller;
public AccountsControllerTests(DatabaseFixture fixture)
{
_fixture = fixture;
_controller = new AccountsControllerV1(_fixture._uow);
}
什么是DatabaseFixture?很高兴你问...
What is DatabaseFixture? Glad you asked...
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(数据传输对象)类分开.我在每次运行和/或测试开始时对内存数据库进行了内存替换,以便在我的数据始终为已知的情况下初始化该文件.我将数据库夹具注入到我的测试类(不是每个测试)中,所以我不会不断创建/销毁.然后,我创建传入数据库UoW定义的控制器.
A few definitions used in the above code. I used a Unit of Work Pattern that contains references to all my EF repositories. I kept Entity (Database) classes and DTO (Data Transfer Object) Classes separate. I used an in-memory replacement for the EF database that I initialize at the beginning of each run and/or test so that my data is always known. I inject the Database Fixture into my test class (not each test) so I am not creating/destroying constantly. Then I create my controller passing in my database UoW definition.
您是 real 控制器,需要注入使用 real 数据库创建的UoW容器.您只是在替换测试中的受控数据库环境.
You're real controller requires injection of the UoW container you've created with the real database. You are merely substituting a controlled database environment for your test.
public AccountsControllerV1(IUoW uow)
{
_uow = uow;
}
是的,我对敏锐的眼睛使用版本控制.是的,这是一个Core 2示例.仍然适用于EF 6,只需要第三方DI;)
And yes, I use versioning for the sharp-eyed. And yes, this is a Core 2 example. Still applicable for EF 6, just need 3rd party DI ;)
我正在测试的控制器方法是什么?
And the controller method I am testing?
[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}");
}
}
这篇关于实体框架模拟需要全局上下文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!