【问题标题】:Mocking 2 or more DBContext calls to the same entity fails模拟对同一实体的 2 个或多个 DBContext 调用失败
【发布时间】: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'尚未在类型'DbSet1Proxy' 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


【解决方案1】:

我最终根据https://stackoverflow.com/a/14368486/495455 使用计数器创建了一个模拟链

int callCounter = 1;
mockContext.Setup(m => m.Employees)
    .Returns(() =>
    {
        if (callCounter == 1)
        {
            callCounter++;
            return employeeToEditMockCU;
        }
        else
        {
            return employeeMockCU;
        }
    });

在第一次模拟后,使用 SetupSequence 进行模拟对我不起作用。 db.Employee 在第一次调用后变为空。所以我不使用 SetupSequence:

mockContext.SetupSequence(x => x.Employees)
.Returns(employeeToEditMockCU)
.Returns(employeeMockCU);

为了绕过AsNoTracking(),我最终在不使用EntityState.Modified的情况下获取了要更新并保存的记录:

EF Update using EntityState.Modified
How to update record using Entity Framework 6?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-24
    • 2020-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多