1

I am new in unit testing. I have a controller - StudentsController with dependency injection and there my Index() method:

public class StudentsController : Controller
{
    public readonly UniversityContext _context;//Database
    public StudentsController(UniversityContext context)
    {
        _context = context;    
    }//Constructor with database
    // GET: Students
    public async Task<IActionResult> Index()
    {
        return View(await _context.Students.ToListAsync());
    }
}

Next i need to write a correct unit test code, that check, if:

1) a View() have a list with my students

2) The query with students is not null.

I read about Mock objects, but I don't know how to write the correct code. My code that I wrote so far:

public class StudentsControllerTests
{
    [Fact]
    public async Task Index_ReturnsAViewResult_WithAListOfStudents()
    {
        var mockRepo = new Mock<UniversityContext>();
        mockRepo.Setup(repo => repo.Students.ToList()).Returns(GetTestStudents());//There i get following error:Expression references a method that does not belong to the mocked object
        var controller = new StudentsController(mockRepo.Object);

        // Act
        var result = controller.Index();

        //// Assert
        var viewResult = Assert.IsType<ViewResult>(result);
        var model = Assert.IsAssignableFrom<IEnumerable<Student>>(
            viewResult.ViewData.Model);
        Assert.NotNull(model);//Second Condition

    }
    public List<Student> GetTestStudents()
    {
        var sessions = new List<Student>();
        sessions.Add(new Student()
        {
            bDate = new DateTime(1994, 7, 2),
            Name = "Test One"
        });
        sessions.Add(new Student()
        {
            bDate = new DateTime(1995, 7, 1),
            Name = "Test Two"
        });
        return sessions;
    }
}

Can someone explain me, how to correct my code?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Eldar
  • 47
  • 8

1 Answers1

2

You only need to mock the members of the context, which in this case is the .Students property. ToList is an extension method being call on the property and cannot be mocked by moq.

Also .Students is a DbSet and would need to be mocked as well.

Using the test classes from this answer :

How to mock an async repository with Entity Framework Core

The following generic extension methods were derived

public static class MockDbSetExtensions {
    public static Mock<DbSet<T>> AsDbSetMock<T>(this IEnumerable<T> list) where T : class {
        IQueryable<T> queryableList = list.AsQueryable();
        Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
        dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
        dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
        dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
        dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(() => queryableList.GetEnumerator());
        return dbSetMock;
    }

    public static Mock<DbSet<T>> ToAsyncDbSetMock<T>(this IEnumerable<T> source)
        where T : class {        
        var data = source.AsQueryable();        
        var mockSet = new Mock<DbSet<T>>();        
        mockSet.As<IAsyncEnumerable<T>>()
            .Setup(m => m.GetEnumerator())
            .Returns(new TestAsyncEnumerator<T>(data.GetEnumerator()));        
        mockSet.As<IQueryable<T>>()
            .Setup(m => m.Provider)
            .Returns(new TestAsyncQueryProvider<T>(data.Provider));        
        mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());        
        return mockSet;
    }

}

With the above utilities, update

 mockRepo.Setup(repo => repo.Students.ToList()).Returns(GetTestStudents());

To

var studentsMockedDbSet = GetTestStudents().ToAsyncDbSetMock();
mockRepo.Setup(repo => repo.Students).Returns(studentsMockedDbSet.Object);
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • There is a problem. "Cannot convert from List to Microsoft.EntityFrameworkCore.DbSet" – Eldar Jul 24 '17 at 09:43
  • @Eldar you are correct I made a mistake. updating answer now – Nkosi Jul 24 '17 at 09:44
  • @Eldar that `ToListAsync` is also goin to cause you some trouble. – Nkosi Jul 24 '17 at 09:55
  • Now i getting the following error: Invalid setup on a non-virtual (overridable in VB) member: repo => repo.Students – Eldar Jul 24 '17 at 10:00
  • @Eldar Check your DbContext and make sure that Students property is `virtual` – Nkosi Jul 24 '17 at 10:01
  • That work, but in test explorer i see following message: Assert.IsType() Failure Expected: Microsoft.AspNetCore.Mvc.ViewResult Actual: System.Threading.Tasks.Task`1 How can i fix that? Thanks – Eldar Jul 24 '17 at 10:08
  • @Eldar that is what I was referring to about the async call. check updated answer. Add the suggested reusable utilities to help with mocking EF in your tests. – Nkosi Jul 24 '17 at 10:10
  • Well, thanks for that, now i read about how to mock an async method with EF. – Eldar Jul 24 '17 at 10:26
  • @Eldar I included a link in the answer to help with that – Nkosi Jul 24 '17 at 10:26
  • I did not handle it. Can you help me with that error? I paste that async methods, but dont know how to use it. – Eldar Jul 25 '17 at 06:59
  • When i change my StudentsController Index() method to the non-async, it perfectly works. But async... – Eldar Jul 25 '17 at 07:08