简体   繁体   中英

Moq and Entity framework

I am trying to find out the best way to do this - I have searched ALOT for a few hours but cannot get this to work. I need a fresh pair of eyes and perspective.

I am trying to create a straight forward app. It will be using EF 6.1.0 as the DAL.

I have an entity called User. I have another entity called WorkItem

a user can have many WorkItems.

I have created a EFDbContext called "TimesheetsContext". It inherits from DbContext and has 2 virtual properties:

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

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

I also have an IUnitOfWork (and its concrete class)

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

The WorkItem and User repository simply has functions such as Add, Login, Update, which in turn calls the EfDBContext to perform the request operation.

Now, I want to create a unit test to be able to mock up the DbContext and be able to add users or work items. However, I cannot seem to get it to work.

Once, with your help, I have got this far, I can then easily change it to use a services layer and pass in an IUnitOfWork.

Using Moq and EntityFramework.Testing.Moq, I cannot get it to work:

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

Finally, I then have a test like this but when I add a user, I still get back 3 and the Assert fails:

        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());

am I going about this the wrong way?

It looks like you are trying to set up users as a backing store, but that's not how Moq works. The mock is returning what you told it to return:

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

If you instead wrote:

users.Add(u);

users.GetEnumerator() would return the four users you expected.

However, it's not clear how this would help you test the object that takes the mock context. I think you should reexamine how the subject under test is being tested, and you may find you don't need to add this object during the course of your test.

It is not clear what concrete class you are tying to test specifically, as you have mocked everything out.

If you want to test your ContextClass (to me seems like you would just be testing 3rd party code which is usually a no no) then you will need to use an integration test which actually hits a database.

Most likely you want some sort of IRepository which has a mocked 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);
      }          
  }

Than in the future you can have a

public class ServiceTimesheetContext : ITimesheetContext { }

Which hits a service rather than a Db

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