4

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?

3
  • And what, exactly, happens? Commented Mar 3, 2015 at 22:58
  • @JohnSaunders - the context still contains the 3 items, and not 4 when I add a new user item. I want to be able to persist and see any updates/changes when invoking the mocked up context/Repository Commented Mar 3, 2015 at 23:18
  • This might interest you a lot: vannevel.net/blog/2015/02/26/11 Commented Mar 4, 2015 at 13:18

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks. Understood. Well, I want to be able to unit test the mocked up context and make sure it can add/remove/update items. So how can I modify the current test setup to enable me to do this from the Test method itself? all the mocking is being done on a method for [TestInitialize] before the tests will run
Why would you want to test a mocked context? Isn't that like testing to see how Moq works? Stubs are intended to test static contexts. For dynamic contexts, you may want to consider creating a custom fake context class with a List as a backing store.
Here's an example of such an object from another question: stackoverflow.com/questions/11787968/…
1

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

2 Comments

Thanks. It does make sense. I guess im trying to get it to the point of being able to say, mock up a service and invoke its methods but the backing store to be of internal collection instead of the DB but not sure how to do that with the IUnitOfWork/EfDBContext
So let me pose another question: how would I mock up a service layer, which in the constructor requires the IUnitOfWork interface (which in turn has the properties to IUserRepository and IWorkItemRepository)? how would I be able to invoke the calls but also be able to persist data in memory for unit testing?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.