简体   繁体   中英

Accessing Moq Mock Data from DbContext disappears when called twice?

I am trying to understand a behavior that is occurring in my application. I have mocked out my DbContext and when I make a call get items from the dbContext.Set<T>().ToList() , the second call does not contain my mocked data. I am not sure why this happens since that data should still exist. Please see code below:

SUT:

public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public decimal Salary { get; set; }
}

public class EmployeeDb
    : DbContext
{
    public EmployeeDb()
    {

    }

    public virtual IDbSet<Employee> Employees { get; set; }
}

UNIT TEST:

public class MockDatabase
{
    public Mock<EmployeeDb> SetMockData()
    {
        var mockDb = new Mock<EmployeeDb>();

        mockDb.Setup(i => i.Set<Employee>()).Returns(GetMockSet(Employees).Object);
        mockDb.SetupGet(i => i.Employees).Returns(() => GetMockSet(Employees).Object);

        return mockDb;
    }
    private List<Employee> _providers;
    private List<Employee> Employees => _providers ?? (_providers = new List<Employee>
    {
        GetEmployee(1),
        GetEmployee(2),
        GetEmployee(3),
        GetEmployee(4),
        GetEmployee(5),
    });
    private static Employee GetEmployee(int id)
    {
        return new Employee
        {
            FirstName = Faker.Name.First(),
            LastName = Faker.Name.Last(),
            Age = Faker.RandomNumber.Next(50),
            Id = id,
            Salary = Faker.RandomNumber.Next(100000)
        };
    }

    #region Hood

    public static Mock<DbSet<T>> GetMockSet<T>(IList<T> items) where T : class
    {
        var querable = items.AsQueryable();
        var mockSet = new Mock<DbSet<T>>();
        mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(querable.Provider);
        mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(querable.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(querable.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(querable.GetEnumerator());
        mockSet.Setup(i => i.Add(It.IsAny<T>())).Callback(delegate (T item) {
            items.Add(item);
        });
        return mockSet;
    }

    #endregion
}


[TestClass]
public class UnitTest1
{
    private EmployeeDb _context;

    [TestInitialize]
    public void TestInitialize()
    {
        var mockDb = new MockDatabase();
        _context = mockDb.SetMockData().Object;
    }
    [TestMethod]
    public void Test_CallTwice_ReturnsEqualCount()
    {
        var emps = _context.Set<Employee>().ToList();
        var emps2 = _context.Set<Employee>().ToList();

        Assert.IsTrue(emps.Count == emps2.Count);

        // -- This works
        //var empCount = _context.Set<Employee>().Count();
        //var empCount2 = _context.Set<Employee>().Count();

        //Assert.IsTrue(empCount == empCount2);
    }
}

Is there something I am not getting about this code? Is there something Moq does with the ToList() ?

ToList enumerates the set when invoked, but the enumerator is forward only, so after the first call it is already at the end.

When setting up the GetEnumerator use the function overload of the Returns in order to allow multiple calls other wise the same enumerator will be returned every time and you get the behavior you experienced.

mockSet.As<IQueryable<T>>()
    .Setup(m => m.GetEnumerator())
    .Returns(() => querable.GetEnumerator()); //<-- function

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