【发布时间】:2019-03-26 04:16:39
【问题描述】:
我有一个 MVC ASP.Net 应用程序,它使用 Entity Framework v6.0 和一个员工表。
我们使用 Code First 方法和标准 Create (CRUD) 方法,该方法具有对现有员工的 EF 查找以及对员工 CreatedBy/ModifiedBy 字段的 EF 查找。 尝试为两者(EF 对象存根)创建模拟失败。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EmployeeID,TeamID,EmployeeADID,FirstName,MiddleName,LastName,EmailAddress,IsAdministrator,IsDeleted")] Employee employee)
{
if (ModelState.IsValid)
{
//Only unique employee IDs
var existingEmployee = db.Employees.FirstOrDefault(o => o.EmployeeADID == employee.EmployeeADID);
if(existingEmployee != null)
{
ViewBag.Error = "Employee ID must be unique, this employee (" + existingEmployee.FullName + ") already exists in the system.";
ViewBag.TeamID = new SelectList(db.Teams, "TeamID", "Name", employee.TeamID);
return View(existingEmployee);
}
SetAuditFields(employee);
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TeamID = new SelectList(db.Teams, "TeamID", "Name", employee.TeamID);
return View(employee);
}
问题在于SetAuditFields 调用,我需要模拟db.Employees.AsNoTracking AsNoTracking 以进行编辑操作。
private void SetAuditFields(Employee employee, bool onlyModified = false)
{
char sep = '\\';
string pID = User.Identity.Name.Split(sep)[1].ToUpper();
var users = db.Employees.AsNoTracking().Where(c => c.EmployeeADID == pID || c.EmployeeID == employee.EmployeeID).ToList();
var currentUser = users.FirstOrDefault(u => u.EmployeeADID == pID);
if (onlyModified)
{
//Notice the AsNoTracking, when you set the db.Entry(object).State = EntityState.Modified; this query wont return anything as its in Modified Mode.
//var originalEmployee = db.Employees.AsNoTracking().FirstOrDefault(o => o.EmployeeID == employee.EmployeeID);
var originalEmployee = users.FirstOrDefault(o => o.EmployeeID == employee.EmployeeID);
employee.CreatedByID = originalEmployee.CreatedByID;
employee.CreatedDate = originalEmployee.CreatedDate;
employee.ModifiedByID = currentUser.EmployeeID;
employee.ModifiedDate = DateTime.Now;
}
else
{
employee.CreatedByID = currentUser.EmployeeID;
employee.CreatedDate = DateTime.Now;
employee.ModifiedByID = currentUser.EmployeeID;
employee.ModifiedDate = DateTime.Now;
}
}
那么在最初模拟 db.Employees 之后,我该如何模拟 db.Employees.AsNoTracking?
下面代码中的这两行注释掉不起作用并失败:
“成员'IQueryable.Provider'尚未在类型'DbSet
1Proxy' which inherits from 'DbSet1'上实现
我也尝试了mockContext.SetupSequence,但我需要在打开和关闭 AsNoTracking 之间进行切换。肯定有我遗漏的东西吗?
[TestMethod()]
public void Create_Submit_Test()
{
// Arrange
var employeeDoesntExist = new Employee { EmployeeID = 0, FirstName = "DoesntExist" };
var employeeAdmin = new Employee { EmployeeID=140, FirstName = "Bern", MiddleName = "", LastName = "O", EmployeeADID = "123", EmailAddress = "Bernard.O@a.com", TeamID = 1, IsDeleted = false, IsAdministrator = true };
var employeeNew = new Employee { FirstName = "Jez", MiddleName = "", LastName = "T", EmployeeADID = "321", EmailAddress = "Jeremy.Thompson@a.com", TeamID = 1, IsDeleted = false, IsAdministrator = true };
var mockContext = new Mock<ICTEntities>();
var employeeEmptyMock = base.GetQueryableMockDbSet(employeeDoesntExist);
var employeeAdminMock = base.GetQueryableMockDbSet(employeeAdmin);
//THESE TWO LINES COMMENTED OUT
//mockContext.Setup(m => m.Employees).Returns(employeeEmptyMock);
//mockContext.Setup(m => m.Employees.AsNoTracking()).Returns(employeeAdminMock);
mockContext.SetupSequence(x => x.Employees.AsNoTracking())
.Returns(employeeEmptyMock)
.Returns(employeeAdminMock);
//I dont want to save it to the Database, otherwise next time we run this the object will already exist, so I mock the call
mockContext.Setup(d => d.SaveChanges()).Returns(1);
var controller = new EmployeesController(mockContext.Object);
controller.ControllerContext = base.MockAccess().Object;
// Act
RedirectToRouteResult result = controller.Create(employeeNew) as RedirectToRouteResult;
// Assert
Assert.IsNotNull(result);
Assert.AreEqual("Index", result.RouteValues["Action"]);
}
这里是 GetQueryableMockDbSet 方法:
protected DbSet<T> GetQueryableMockDbSet<T>(params T[] sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
return dbSet.Object;
}
【问题讨论】:
-
看看这个 (stackoverflow.com/questions/41324783/…) 示例设置模拟的方式,以及他们在异步查询中遇到的问题的修复。 (因为这很可能会出现。)他们配置模拟有点不同。就我个人而言,我使用存储库模式作为截止点进行模拟,但使用该 DbAsyncEnumerable 模式来包装存根数据。
-
@StevePy 谢谢,我经历过,它实际上来自Using Mocking to create a In Memory DB,以及它的姊妹文章Manually creating a InMemory dB。我不想真正进入关于孤立单元测试的哲学,否则它是一个集成测试。通常我遵循存储库/存储模式,但这次我只是决定模拟数据库。我快到了,我有 32 次测试通过了,而只有 1 次测试失败了。
-
AsNoTracing是一种扩展方法。嘲笑dbSet... -
@Johnny 我该怎么做? 我通常使用 NSubstitute。
-
@JeremyThompson 您是否考虑过 Effort (entityframework-effort.net) 对您的 EF 代码进行单元测试?在我们的项目中,我们了解到与自己模拟 DbSet 相比,努力更容易使用。
标签: c# entity-framework duplicates moq