Making ASP.NET MVC code more testable using a Service Layer.

Common wisdom says regarding the Repository and Unit of Work patterns in ASP.NET MVC to implement it on top of Entity Framework. This is supposed to make your code more testable and more decoupled, which is always desirable.

Doing this, however, can be complicated. The general architecture of an application implementing these patterns (as seen on MSDN) is shown below:

Windows-Live-Writer_8c4963ba1fa3_CE3B_Repository_pattern_diagram_1df790d3-bdf2-4c11-9098-946ddd9cd884

Implementing all of these classes can of course be daunting. Trying to write testable, modular code and following best practices might  lead to an explosion of classes, especially if you’re trying to follow the Repository pattern and what not.

There is a simpler way to do this. Implementing only a Service layer allows you to write more testable, modular code without having to write several different layers of classes.

Now, the standard architecture that ASP.NET MVC generates with scaffolding looks like this:

standard-m-v-c

The Model is used by the Controller to render into the View. Much of the code you’ll need is auto-generated but it’s very hard to test if you need to add your own business logic.

If you write Services however, classes that encapsulate Entity Framework stuff to use in your controller, you have a mockable, testable chunk of business logic which can decouple much of your application. It would look something like this:

service-example (1)

Each model would have a service. However, you can have a Service for a logical group of Models as well, that is up to you. Each Service will implement an interface, which can be mocked in unit tests and injected into the controllers.

For example, if you have a Student model and an Instructor model such that one Instructor has many Students and each model has it’s own controller. We can write a Service class for each model that uses the DbContext as it’s repository and unit of work. This way we will still be using the Repository and Unit of Work patterns, but not repeating stuff.

These services can then be injected into the Controller as you see fit and the reason we define interfaces for each service, is precisely so we can inject mock implementations of the services in the Controller.

Consider our Student-Instructor example. Say that you want a list of Students for that Instructor displayed when the user views details about that Instructor. You want this list to be always arranged by the Students’ names in alphabetical order.

We write a function in the InstructorService class to do this, let’s call it GetStudents(int instId) that simply returns a list of students with that instructor. It may seem simple but there are a lot of benefits to having such small functions. The first being that we don’t have to re-write the query each time we use it. For instance, we can use it in a normal Action method or we could use it in a Web API controller.

The normal Details action method, that renders a View that shows Instructor Details would be modified thus:

// GET: Instructors/Details/5
public ActionResult Details(int? id)
{
        if (id == null)
        {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        // I'm using id ?? 0 to convert int into a nullable int.
        Instructor instructor = instructorService.FindById(id ?? 0);
        ViewBag.students = instructorService.GetStudents(id ?? 0);

        if (instructor == null)
        {
                return HttpNotFound();
        }
        return View(instructor);
}

Where the instructorService object is injected into the Controller or initialized somewhere else. I’m using a ViewBag here but I should probably use ViewModels.

Regardless, we can easily test the behavior of our Details action method using the following test:

[TestMethod]
public void TestMethod1()
{
        // Arrange
        Mock mock = new Mock();
        Instructor testInstructor = new Instructor() { Id = 1, Name = "Test Instructor" };
        Student testStudentA = new Student()
        {
                Id = 100,
                Name = "AAA",
                Instructor = testInstructor
        };

        Student testStudentB = new Student()
        {
                Id = 100,
                Name = "BBB",
                Instructor = testInstructor
        };

        mock.Setup(m => m.GetStudents(It.IsAny()))
                .Returns(new List() { testStudentA, testStudentB });

        mock.Setup(m => m.FindById(It.IsAny())).Returns(testInstructor);

        // This List is already arranged alphabetically.
        List expectedStudents = new List()
        {
                testStudentA,
                testStudentB
        };

        // Act
        InstructorsController controller = new InstructorsController(mock.Object);
        var result = controller.Details(testInstructor.Id) as ViewResult;
        List returnedStudents = result.ViewBag.students;

        // Assert
        CollectionAssert.AreEqual(expectedStudents, returnedStudents);
}

The test above may see large but a lot of it is simply initialization code, that would go in a different method. This test simply makes sure that the ViewBag contains the proper students.

What’s important to note here is that we are testing the controller and only the controller. Therefore we create Mocks of all of its dependencies, which in this case is the IInstructorService interface.

This means that we can also test the Service classes if we mock the DbContext class, i.e. the Model which they depend on.

An example of such a test, that tests the GetStudents() method is given below. I’ve omitted a lot of the setup code this time, so it should be easier to follow.

[TestMethod]
public void TestGetStudents()
{
        //
        Debug.WriteLine(mockDbSetInstructors.Object);
        List students = new List() { testStudentB, testStudentA, };
        Mock mock = new Mock();
        mock.Setup(m => m.GetAll()).Returns(students);

        // This list is in alphabetical order
        List expectedStudents = new List() { testStudentA, testStudentB };

        // Act
        // Real implementation of Instructor Service
        InstructorService service = new InstructorService(mockCtx.Object, mock.Object);
        List returnedStudents = service.GetStudents(testInstructor.Id).ToList();

        // Assert
        CollectionAssert.AreEqual(expectedStudents, returnedStudents);
}

Thus, with the inclusion of the Service classes, we don’t have too much of an explosion of classes and our code is a lot more modular and testable than it previously was.

Of course eventually you might have to use ViewModels and AutoMapper and other stuff, but simply having Service objects such as these can make things a lot more testable.

Advertisements

A test-driven approach to developer communication

In teams that are distributed or partially distributed, there often is a need for a centralized online meeting point where everyone can discuss and share information about what they’re working on. Stuff like WhatsApp groups, Trello boards, github issue discussions and the classic mailing lists are some of the tools already in place for this.

For a team that I was working with which comprised mostly of students working on a mock project I needed a way to make sure that everyone was always on the same page and there was some written record of what the teams needed from each other, so that the teams didn’t have to come in and talk to each other all the time.

We were developing an application using the ASP.NET MVC Platform and we had a frontend team and a backend team working in tandem. We needed a way to make sure the frontend team communicated very clearly what data (in the form of Models, URLs, etc.) they needed from the backend team.

We had a design document and UI diagrams and ER diagrams and all of that already built, however the precise details of what exact object would be returned to the Views couldn’t be easily documented. For example, say for the Books list page, they need a Book Object that may look like this:


class Book
{
public virtual Item ItemId { get; set; } // Foreign Key
public int BookId { get; set; } // primary key
public string Title { get; set; }
public string Author { get; set; }
public DateTime DateOfPublishing { get; set; }
}

There were also common business logic rules that the frontend and the backend teams had to ensure, say, the ItemID, which was a foreign key could not be null.

Of course there’s a lot of ways to communicate this, I had a crazy idea. Rather than only have documents and diagrams that everyone has to be told about and explained and are difficult to maintain, we also right tests. The frontend team writes a test for the Book object that it needs from the controller and the backend team delivers that object, thus making the tests pass.

This way, the test would be the common format of communication, that teams use to officially tell each other what they need. The tests wouldn’t replace the documentation that already existed, but rather augment it.

So the scenario would go like this: The frontend team needs a list, specifically C#’s IEnumerable of Books, whose ItemID’s are not null. They write a test that looks like the following psuedocode:


public void BookListTest
{
// code to set up test

books = controller.List();
Assert.IsType(IEnumerable, books);
Assert.IsNotNull(books.Any(b => b.ItemId));
Assert.IsNotNull(books.Any(b => b.DateOfPublishing));
}

The test above says simply that the List() method, i.e. the List View of the controller returns a) An IEnumberable of Book objects, b) the ItemId for any book is not null and c) the DateOfPublishing for any book is not null.

Of course most of these rules would already be documented in the business logic, the tests just makes sure that when their built, that stuff is taken care of.

Both the frontend people and the backend people look at this test and go, “That’s what I’m getting, that’s what I need to do”. This is included in the code and this is a test. When it is first written, the test fails. Eventually it passes and everyone moves on to the next controller or what have you.

The idea came really from studying about how computer systems that aren’t made with the same architecture communicate. For example, consider how objects are serialized and sent over to other application servers in Java. They’re converted to a specific format that every system knows and then sent over to the other instance.

Consider this a way of serializing requirements. They’re codified and cemented in the form of tests so everyone is always aware of them. If there’s a flaw in the tests, they should be updated and that change is added into the code.

This approach, of course, has flaws. The biggest flaw that we found in our project was that the tests were often wrong, so they had to be updated to pass. However, this gave everyone who read the test a clearer understanding of what the component did.

In fact, you wouldn’t be completely wrong to think that this is pointless. Projects already have sequence diagrams and use cases and other detailed documentation that already solve this problem. This approach requires you to write a lot of tests, since this is technically a form of Test-Driven Development.

However, the primary benefit of this approach, is that now you have tests. It is correct that you need additional development time to write and debug tests. It is also correct that projects can get by without this approach because diagrams and documents, while not flawless work just fine. They can be a pain sometimes and this approach doesn’t solve all the problems of asynchronous development and distributed teams. However, with this approach, now you have tests!

In no way am I suggesting that this replace whatever already is available to any team in the world. However, writing tests can simply be a way to tell everyone on the team, exactly what needs to be done. This approach is meant to more augment existing documentation rather than replace it.

To verify the absolute effectiveness of this approach, there needs to be a proper study looking at data over a significant period of time. I hope you’ve found this article interesting and I believe this idea is worth looking into. I’m currently writing a paper about this approach and hopefully it all works out.