简体   繁体   中英

Mocking 2 or more DBContext calls to the same entity fails

I've got an MVC ASP.Net app using Entity Framework v6.0 with an Employee's table.

We're using a Code First approach with the standard Create (CRUD) method that has a EF lookup for existing employee's and also an EF lookup for employee CreatedBy/ModifiedBy fields. Trying to create mocks for both (EF object stubs) fails.

[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);
}

The problem is the SetAuditFields call and I need to mock db.Employees.AsNoTracking AsNoTracking for the Edit operation.

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;
    }
}

So how do I mock db.Employees.AsNoTracking after initially mocking db.Employees ?

The THESE TWO LINES COMMENTED OUT in the code below don't work and fail with:

"The member 'IQueryable.Provider' has not been implemented on type 'DbSet 1Proxy' which inherits from 'DbSet 1'

I also tried the mockContext.SetupSequence but I need to interchange between with AsNoTracking on and off. Surely there must be something I'm missing?

[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"]); 
}

Here is the GetQueryableMockDbSet method:

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;
}

I ended up creating a chain of mocks using a counter as per https://stackoverflow.com/a/14368486/495455

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

Mocking using a SetupSequence doesn't work for me after the first mock. The db.Employee becomes null after the first call. So I dont use a SetupSequence:

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

Also to get around the AsNoTracking() I ended up fetching the record to update and saving it without using EntityState.Modified :

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM