From Legacy to Dependency Injection

by Just* Team | Comments 8

The best thing about using Telerik JustMock is that you can mock practically everything. You can even mock types without using dependency injection, making JustMock the best framework to use when working with legacy code.

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!

About the author

Chris Eargle

Chris Eargle

is a Telerik Technical Evangelist and Microsoft C# MVP with over a decade of experience designing and developing enterprise applications, and he runs the local .NET User Group: the Columbia Enterprise Developers Guild. He is a frequent guest of conferences and community events promoting best practices and new technologies. Chris is a native Carolinian; his family settled the Dutch Form region of South Carolina in 1752. He currently resides in Columbia with his wife, Binyue, his dog, Laika, and his three cats: Meeko, Tigger, and Sookie. Amazingly, they all get along... except for Meeko, who is by no means meek.

Posted in: unit-testing

8 Comments

Jonathan Allen
Where is the rest of the test?
All you have asserted is that the count is five. That's the easy part, you have to verify that those five items were actually populated.
You also have four assumptions in your test (two null checks and two type casts). Those should be explicit assertions. Otherwise you won't be able to tell the difference between "books" being null and "result" being null.    
Chris Eargle

Hi Jonathan,
I am just using this to show how you can use JustMock to test legacy code prior to creating a seam with dependency injection.  When refactoring, it's always key to have all of your code under test before making changes so you get rapid feedback on the success/failure of the change, and JustMock allows you to do that with future mocking.
I then show you how to move away from using the profiled version of JustMock to the non-profiled version (thus getting significant improvements in performance) after you have successfully introduced constructor injection into the system under test.
As for the rest of the test, it was probably not the best example of how I would write a unit test in production, but when illustrating a specific point, I am careful not to introduce too much into the sample.  It could become noise that obfuscates the point I am trying to illustrate.
Thanks you for your feedback, and let me know if you would like me to cover a specific topic in the future.

Alexander
Why do we need interface here?
The usual reason for creating an interface is that most of mock frameworks do not support class mocking, but as I understood, it is not a problem for JustMock
Ferret
Congratulations Jonathan - you completely missed the point!
Chris Eargle
You're right Alexander, mocking classes is not a problem for JustMock, which is why it works so well for testing tightly-bound code. You create interfaces to achieve loose coupling and better test performance.
rococo
With so many free Mocks, this mock will go nowhere.
Chris Eargle
Hi rococo, there is certainly a lot of competition in the free mocking framework space, and we provide a free version of JustMock as well. The commercial edition of JustMock is compelling because it offers the ability to mock many things that free mocking frameworks are unable, including classes tightly bound to other classes.
You can use the free edition of JustMock in projects where you would have used another free mocking framework in the past. If you find yourself on a project where the commercial edition is necessary, you won't need to learn a new framework.
Ferret
Rococo - a free mocking framework backed by a company with a solid track record - I don't see why it would go nowhere. We've moved from Rhinomocks to JustMock largely on the back of already using Telerik's WinForms suite, reporting and JustCode, and having always had good support and relatively speedy resolution to most issues. The legacy testing opportunities opened up by the examples in this post are a perfect example of why it's worth using JustMock with a view to down-the-road even though we don't need the upgraded license for now.

Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by Just* Team - or - read latest articles in Developer Tools
Product Families