【问题标题】:Moq and Entity framework起订量和实体框架
【发布时间】:2015-03-03 22:48:24
【问题描述】:

我正在尝试找出最好的方法 - 我已经搜索了几个小时,但无法让它发挥作用。我需要一双全新的眼睛和视角。

我正在尝试创建一个简单的应用程序。 它将使用 EF 6.1.0 作为 DAL。

我有一个名为 User 的实体。 我有另一个名为 WorkItem 的实体

一个用户可以拥有多个 WorkItem。

我创建了一个名为“TimesheetsContext”的 EFDbContext。它继承自 DbContext 并具有 2 个虚拟属性:

    public virtual IDbSet<WorkItem> WorkItems { get; set; }

    public virtual IDbSet<User> Users { get; set; }

我还有一个 IUnitOfWork(及其具体类)

public interface IUnitOfWork
{
    ITimeSheetContext Context { get; set; }
    IWorkItemRepository WorkItemRepository { get; set;  }
    IUserRepository UserRepository { get; set; }
}

WorkItem 和 User 存储库仅具有 Add、Login、Update 等功能,这些功能又调用 EfDBContext 来执行请求操作。

现在,我想创建一个单元测试,以便能够模拟 DbContext 并能够添加用户或工作项。但是,我似乎无法让它工作。

一旦在您的帮助下,我已经做到了这一点,我可以轻松地将其更改为使用服务层并传入 IUnitOfWork。

使用 Moq 和 EntityFramework.Testing.Moq,我无法让它工作:

    private Mock<TimeSheetContext> _mockContext;

    // <snip />

        Guid userId1 = Guid.NewGuid();
        Guid userId2 = Guid.NewGuid();
        Guid userId3 = Guid.NewGuid();
        var users = new List<User>(new[] 
        { 
            new User { Firstname = "Joe", Lastname = "Bloggs", Password = "pass1", UserId = userId1, Username = "JoeBloggs" },
            new User { Firstname = "Thom", Lastname = "Stevens", Password = "pass2", UserId = userId2, Username = "ThomStevens"},
            new User { Firstname = "Homer", Lastname = "Simpson", Password = "pass3", UserId = userId3, Username = "HomerSimpson" }
        }).AsQueryable();

        var tasks = new List<LionTask>(new[] 
        { 
            new WorkItem { CreatedDate = DateTime.Today, Description = "Desc1", ModifiedDate = DateTime.Today, State = 1, TaskId = Guid.NewGuid(), Title = "Test Title", UserId = userId1 },
            new WorkItem { CreatedDate = DateTime.Today, Description = "Desc2", ModifiedDate = DateTime.Today, State = 2, TaskId = Guid.NewGuid(), Title = "Test Title 2", UserId = userId2 },
            new WorkItem { CreatedDate = DateTime.Today, Description = "Desc3", ModifiedDate = DateTime.Today, State = 3, TaskId = Guid.NewGuid(), Title = "Test Title 3", UserId = userId3 }
        }).AsQueryable();

        this._mockContext = new Mock<TimeSheetContext>();
        var taskDbSetMock = new Mock<IDbSet<WorkItem>>();
        var userDbSetMock = new Mock<IDbSet<User>>();
        userDbSetMock.As<IQueryable<User>>().Setup(m => m.Provider).Returns(users.Provider);
        userDbSetMock.As<IQueryable<User>>().Setup(m => m.Expression).Returns(users.Expression);
        userDbSetMock.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(users.ElementType);
        userDbSetMock.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(users.GetEnumerator());

        taskDbSetMock.As<IQueryable<WorkItem>>().Setup(m => m.Provider).Returns(tasks.Provider);
        taskDbSetMock.As<IQueryable<WorkItem>>().Setup(m => m.Expression).Returns(tasks.Expression);
        taskDbSetMock.As<IQueryable<WorkItem>>().Setup(m => m.ElementType).Returns(tasks.ElementType);
        taskDbSetMock.As<IQueryable<WorkItem>>().Setup(m => m.GetEnumerator()).Returns(tasks.GetEnumerator());

        this._mockContext.Setup(c => c.Users).Returns(userDbSetMock.Object);
        this._mockContext.Setup(c => c.WorkItems).Returns(taskDbSetMock.Object);

最后,我进行了这样的测试,但是当我添加用户时,我仍然返回 3 并且断言失败:

        User u = new User { Firstname = "Timmy", Lastname = "Johanson", Password = "omg123", UserId = Guid.NewGuid(), Username = "TJ" };
        this._mockContext.Object.Users.Add(u);

        Assert.AreEqual(4, this._mockContext.Object.Users.Count());

我是不是走错了路?

【问题讨论】:

  • 究竟会发生什么?
  • @JohnSaunders - 当我添加一个新的用户项目时,上下文仍然包含 3 个项目,而不是 4 个。我希望能够在调用模拟的上下文/存储库时坚持并查看任何更新/更改
  • 这可能会让你很感兴趣:vannevel.net/blog/2015/02/26/11

标签: c# entity-framework unit-testing moq


【解决方案1】:

您似乎正在尝试将 users 设置为后备存储,但这不是 Moq 的工作方式。模拟正在返回您告诉它返回的内容:

userDbSetMock.As<IQueryable<User>>().Setup(m => m.GetEnumerator())
  .Returns(users.GetEnumerator());

如果你改为写:

users.Add(u);

users.GetEnumerator() 将返回您期望的四个用户。

但是,尚不清楚这将如何帮助您测试采用模拟上下文的对象。我认为你应该重新检查被测对象是如何被测试的,你可能会发现你不需要在测试过程中添加这个对象。

【讨论】:

  • 谢谢。明白了。好吧,我希望能够对模拟的上下文进行单元测试,并确保它可以添加/删除/更新项目。那么如何修改当前的测试设置以使我能够从测试方法本身执行此操作?在测试运行之前,所有的模拟都在 [TestInitialize] 的方法上完成
  • 为什么要测试模拟上下文?这不就像测试看看 Moq 是如何工作的吗?存根旨在测试静态上下文。对于动态上下文,您可能需要考虑创建一个自定义的假上下文类,并将 List 作为后备存储。
  • 这是另一个问题中此类对象的示例:stackoverflow.com/questions/11787968/…
【解决方案2】:

目前尚不清楚您要专门测试什么具体类,因为您已经模拟了所有内容。

如果您想测试您的 ContextClass(在我看来,您只是在测试通常是不可以的 3rd 方代码),那么您将需要使用实际命中数据库的集成测试。

你很可能想要某种IRepository,它有一个模拟的TimesheetsContext

public interface ITimesheetsContext
{
    IDbSet<Timesheet> Timesheets { get; set; }
}
public interface ITimesheetRepository
{
    void Add(Timesheet sheet);
}

public class DbTimesheetRepository : ITimesheetRepository
{
    public ITimesheetsContext _context;

    public DbTimesheetRepository(ITimesheetsContext context)
    {
        _context = context;
    }

    public void Add(Timesheet ts)
    {
        _context.Timesheets.Add(ts);
    }
}

  [TestFixture]
  public class DbTimesheetRepositoryTests
  {
      public void Add_CallsSetAdd()
      {
          // Arrange
          var timesheet = new Timesheet();

          var timesheetsMock = new Mock<IDbSet<Timesheet>>();
          timesheetsMock.Setup(t => t.Add(timesheet)).Verifiable();

          var contextMock = new Mock<ITimesheetsContext>();
          contextMock.Setup(x => x.Timesheets).Returns(timesheetsMock.Object);

          var repo = new DbTimesheetRepository(contextMock.Object);

          // Act
          repo.Add(timesheet);

          // Assert
          timesheetsMock.Verify(t => t.Add(timesheet), Times.Once);
      }          
  }

比以后你可以拥有一个

public class ServiceTimesheetContext : ITimesheetContext { }

哪个击中的是服务而不是 Db

【讨论】:

  • 谢谢。这确实有道理。我想我试图让它能够说,模拟一个服务并调用它的方法,但是后备存储是内部集合而不是 DB,但不确定如何使用 IUnitOfWork/EfDBContext
  • 所以让我提出另一个问题:我将如何模拟一个服务层,它在构造函数中需要 IUnitOfWork 接口(它又具有 IUserRepository 和 IWorkItemRepository 的属性)?我如何能够调用这些调用,同时又能够将数据保存在内存中以进行单元测试?
猜你喜欢
  • 2015-12-23
  • 2014-11-18
  • 2012-06-01
  • 2012-06-25
  • 2011-04-05
  • 2012-11-27
  • 2013-02-10
  • 2011-10-11
  • 2020-08-12
相关资源
最近更新 更多