Unit test a method that sorts a collection

2019-08-12 14:33发布

问题:

I have a method that sorts a collection based on a property like this:

public List<Student> GetAllStudents()
{
    return _studentCatalogContext.Student.Where(x => (x.Course != 2 && x.Course != 6))
                                 .OrderByDescending(x => x.EnrollDateTime).ToList();
}

So the idea is to have, in this case, the most recently enrolled Student first.

Since the result of the method call will be a sorted list with the newest enrollment first I wrote a test like this:

[TestMethod]
public void Calling_GetAllStudents_ReturnsSortedListOfStudents()
{
    var studentsList = new List<Student> {
    new Student {
                     Id = "123",
                     EnrollTime = "02/22/16 14:06:56 PM",
                     Course = 1
                 },
     new Student {
                     Id = "456",
                     EnrollTime = "03/30/16 12:50:38 PM",
                     Course = 3
                 }
                 };

    _studnentRepository.Setup(x=>x.GetAllStudents()).Returns(studentsList);

    Assert.AreEqual("02/22/16 14:06:56 PM", studentsList[0].EnrollTime);
}

It's been suggested that this test does is not valid in that it sets a value and then asserts on it.

How would I write a correct unit test in this case?

回答1:

Testing that a list is sorted correctly is largely irrelevant because it is an in-built framework method that (you would assume) has been tested and proven correct by the framework designers (Microsoft in this case).

A better test for this method would be to ensure that only students who are not in Course 2 or 6 are returned, since that is your custom logic inside the Where method.

So, your test could be something like:

[TestMethod]
public void Calling_GetAllStudents_ReturnsSortedListOfStudents()
{
    var studentsList = new List<Student> {
    new Student {
                     Id = "123",
                     EnrollTime = "02/22/16 14:06:56 PM",
                     Course = 1
                 },
     new Student {
                     Id = "456",
                     EnrollTime = "03/30/16 12:50:38 PM",
                     Course = 2
                 }
                 };

    // mock out student repository to return list    

    var studentsList = _studentRepository.GetAllStudents();

    Assert.AreEqual(1, studentsList.Count);
    Assert.AreEqual("123", studentsList[0].Id);
}


回答2:

You test as it stands is a null test (it tests that the list you constructed has the first element that you just put there).

In it's current form, your accepted answer has the same problem. It sets up a mock of your repository and then validates that the mock returns the information that you've told the mock to.

To test the repositories logic effectively, you need to mock its dependencies, which in this case is the _studentCatalogContext. Since you haven't provided this in your question, I'm going to assume the following classes:

public class Student {
    public string Id {get;set;}           // Weird this is a string not int
    public string  EnrollTime {get;set;}  // Weird this is a string not date
    public int Course  {get;set;}
}

public class StudentCatalogContext : DbContext
{
    public virtual IDbSet<Student> Student { get; set; }
}

public class StudentRepository
{
    private StudentCatalogContext _studentCatalogContext;
    public StudentRepository(StudentCatalogContext context)
    {
        _studentCatalogContext = context;
    }
    public List<Student> GetAllStudents()
    {
        return _studentCatalogContext.Student.Where(x => (x.Course != 2 && x.Course != 6))
                                     .OrderByDescending(x => x.EnrollTime).ToList();
    }
}

Notice, I am injecting the catalog context into the repository. This is required, so that it can be mocked. Also, notice that I've renamed EnrolledDateTime in your LINQ query to EnrollTime so that it matches the rest of your code sample.

This code can then be tested as follows:

// Construct the data to be returned by the student set mock.
// Note, you don't want this to be in the order that you're expecting
// otherwise, how do you know if it's been sorted...
var studentData = new List<Student> {
    new Student {
                     Id = "123",
                     EnrollTime = "02/22/16 14:06:56 PM",
                     Course = 1
                 },
     new Student {
                     Id = "456",
                     EnrollTime = "03/30/16 12:50:38 PM",
                     Course = 3
                 }
}.AsQueryable();

// Setup a mock of the student set, which returns the canned data
// prepared above
var dbSetMock = new Mock<IDbSet<Student>>();
dbSetMock.Setup(m => m.Provider).Returns(studentData.Provider);
dbSetMock.Setup(m => m.Expression).Returns(studentData.Expression);
dbSetMock.Setup(m => m.ElementType).Returns(studentData.ElementType);
dbSetMock.Setup(m => m.GetEnumerator()).Returns(studentData.GetEnumerator());

// Create a mock of the catalog context that returns
// the mocked set prepared above
var contextMock = new Mock<StudentCatalogContext>();
contextMock.Setup(x=>x.Student).Returns(dbSetMock.Object);

// Create the system under test, injecting the mock context
var repo = new StudentRepository(contextMock.Object);

// Call the method that we're actually testing
var fetchedData = repo.GetAllStudents();

// Validate that the information returned is what we're expecting
Assert.AreEqual("02/22/16 14:06:56 PM", fetchedData[0].EnrollTime);

It's worth pointing out that the above test fails. This is because the code under test sorts EnrollTime descending, so the first item in the list is the one with the EnrollTime of "03/30/16 12:50:38 PM"