Tuesday, December 1, 2020

From Legacy to Dependency Injection

We’ve all encountered tightly-bound code, and our first instinct is to correct it. However, there are only so many hours in a sprint, and it’s not always convenient to go on a large refactoring spree when the backlog is filling up. With JustMock, you can still ensure the code works, and it will set you up for the cleaning that will take place at a later time.

The code I am about to show is rather simplistic, and it may not be much of a burden to refactor when encountered. The principles are the same in testing the code then refactoring to achieve higher quality.

Here is a simple action on an ASP.NET MVC controller:

public ActionResult Index()
{
    var repository = new BookRepository();
    var books = repository.GetAll();
    return View(books);
}

These three lines of code retrieves all books from a BookRepository and passes them to the view. Testing this controller action doesn’t appear to be very difficult:

[Fact]
public void Index_Should_Return_All_Books_To_View()
{
    BookController controller = new BookController();
    var result = (ViewResult)controller.Index();
    var books = (IEnumerable<BookModel>)result.Model;
    Assert.Equal(5, books.Count());
}

This test has a major problem: it must call all the way to the backing data storage. I have been in many organizations that attempt to get around this by creating a “test” database. This approach leads to even more issues. First, the lack of code isolation means it can be more time consuming to track down the cause of a bug. If this test doesn’t work, I will need to figure out if this code is broken, if the repository is broken, or if there’s bad code in another layer that isn’t immediately noticeable. Next, it takes more work to make sure side effects in the database do not affect unrelated. It can also be troublesome to maintain the data. Finally, testing through every layer of the application is slow. When the application grows large and the tests become more numerous, it becomes a chore to run them… and you might as well forget automating it on check-in.

Here’s how to use JustMock to isolate this unit of code without refactoring.

[Fact]
public void Index_Should_Return_All_Books_To_View()
{
    var repository = Mock.Create<BookRepository>();
     
    repository.Arrange(r => r.GetAll())
              .Returns(Enumerable.Repeat<BookModel>(new BookModel(), 5))
              .IgnoreInstance();
 
    BookController controller = new BookController();
    var result = (ViewResult)controller.Index();
    var books = (IEnumerable<BookModel>)result.Model;
    Assert.Equal(5, books.Count());
}

 The first step was to create a mock object of the BookRepository used in the controller action. There isn’t an interface for this class, so it’s a good thing that JustMock will mock a concrete class. Next, I arranged for the repository to return a sequence of books when the GetAll() method is called on it. I then used IgnoreInstance() so that every instance of BookRepository encountered in this test would be replaced with the mock object.

It was that easy, and you’re now in a position to refactor the code to support dependency injection with minimal changes to your test.

First, we should give the BookRepository an interface (you can cheat and use the Extract Interface refactoring).

public interface IBookRepository
{
    IEnumerable<BookModel> GetAll();
}

 Next, promote the repository variable to a field (Introduce Field refactoring) and change its type to IBookRepository. Create two constructors: one with no parameters and one with an IBookRepository parameter. Chain the default constructor to the one with a parameter, passing in new BookRepository. In the constructor with a parameter, set the field to the passed in value.

public class BookController : Controller
{
    private readonly IBookRepository repository;
 
    public BookController()
        this(new BookRepository())
    {
    }
 
    public BookController(IBookRepository repository)
    {
        this.repository = repository;
    }
 
    public ActionResult Index()
    {
        var books = repository.GetAll();
        return View(books);
    }
}

In the unit test, remove the call to IgnoreInstance() and pass the mocked repository into the BookController’s constructor.

[Fact]
public void Index_Should_Return_All_Books_To_View()
{
    var repository = Mock.Create<BookRepository>();
 
    repository.Arrange(r => r.GetAll())
              .Returns(Enumerable.Repeat<BookModel>(new BookModel(), 5));
 
    BookController controller = new BookController(repository);
    var result = (ViewResult)controller.Index();
    var books = (IEnumerable<BookModel>)result.Model;
    Assert.Equal(5, books.Count());
}

Conclusion

In this article, you learned how to mock objects without changing the code base. You also learned one of the easiest techniques for introducing dependency injection in your project while causing minimal changes to your existing tests. There’s a wide world of best practices for creating loosely coupled architectures that are conductive to unit testing. I hope this helps you get started… happy coding!

No comments:

Post a Comment

React Profiler: A Step by step guide to measuring app performance

  As react applications are becoming more and more complex, measuring application performance becomes a necessary task for developers. React...