Click here to Skip to main content
15,889,096 members
Articles / Web Development / HTML

ASP.NET MVC : Injecting & Mocking an IRepository

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
5 Mar 2010CPOL3 min read 28.7K   10  
If you are already familiar with ASP.NET MVC, you have probably seen the repository pattern in a few examples (such as NerdDinner), maybe you even use it in your own app.One of the most frequent way to hot-plug a Repository class into a controller is to use the Strategy Pattern.

If you are already familiar with ASP.NET MVC, you have probably seen the repository pattern in a few examples (such as NerdDinner), maybe you even use it in your own app.

One of the most frequent way to hot-plug a Repository class into a controller is to use the Strategy Pattern. But while an application evolves, you might want to centrally manage your wiring within a provider, that's what Dependency Injectors are for. However, lots of Dependency Injectors - or IoC containers at large - such as Unity or Castle Windsor contain a plethora of features and often rely heavily on XML configuration, making them overkill for smaller projects. Ninject makes it relatively effortless to set up a DI. It just reached version 2.0.

In this post we will see how to:

  • Quickly inject a Repository in a controller with Ninject
  • Mock the IRepository interface in your tests with the Ninject.Moq plugin

Get the Bits

Here's where to get these dependencies: Moq Ninject2 (with Ninject.Web.Mvc) Ninject.Moq plugin (download and build)

Creating a Simple Repository

Let's start a quick MVC app with a Repository. The Winter Olympics provide us with a simple Model composed of the following types:

Our Repository class looks like this:

C#
<textarea name="code" class="c#" cols="60" rows="10">

public class Repository
{
public IEnumerable<athlete> Athletes
{
get
{
return FakeDb.Athletes;
}
}

public Athlete GetAthleteByName(string name)
{
var ath = from a in FakeDb.Athletes
where a.LastName == name
select a;
return ath.Single();
}

public IEnumerable<athlete> GetAthletesByDiscipline(string discipline)
{
var ath = from a in FakeDb.Athletes
where a.Discipline.Name == discipline
select a;
return ath;
}

public IEnumerable<athlete> GetAthletesByCountry(string country)
{
var ath = from a in FakeDb.Athletes
where a.Country.Name == country
orderby a.Discipline.Name
select a;
return ath;
}

}

</textarea>

Of course the first thing we'll do is to extract an IRepository interface from this class. We can at this point create a few Action Methods in an AthletesController to send some data to the view.

C#
<textarea name="code" class="c#" cols="60" rows="10">
public interface IRepository
{
IEnumerable<athlete> Athletes { get; }
Athlete GetAthleteByName(string name);
IEnumerable<athlete> GetAthletesByCountry(string country);
IEnumerable<athlete> GetAthletesByDiscipline(string discipline);
}
</textarea>

Injecting an IRepository into a Custom Controller

We'll now create a custom Controller by extending the MVC Controller class. Our extended type will contain our injected IRepository and all our controllers will inherit from it. Notice the [Inject] attribute:

C#
<textarea name="code" class="c#" cols="60" rows="10">
public class OlympicsController : Controller
{
[Inject]
public IRepository Repository;
}
</textarea>

We only need two things to set up Ninject and bind the Repository implementation as an IRepository in our controller instance:

First, a Ninject Module class where the binding occurs:

C#
<textarea name="code" class="c#" cols="60" rows="10">

namespace WinterOlympics
{
public class WebModule : NinjectModule
{
public override void Load()
{


//Repository injection
Bind<irepository>()
.To<repository>();

}
}
}
</textarea>

Second, we need to change our Global.asax so our MvcApplication inherits from NinjectHttpApplication instead. That's also where the override CreateKernel() references our binding module. Finally, we'll move the Application_Start content under the OnApplicationStarted override and call RegisterAllControllersIn() with the current assembly as parameter.

C#
<textarea name="code" class="c#" cols="60" rows="10">
namespace WinterOlympics
{

public class MvcApplication : NinjectHttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);

}

protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();

RegisterRoutes(RouteTable.Routes);
RegisterAllControllersIn(Assembly.GetExecutingAssembly());
}

protected override IKernel CreateKernel()
{
return new StandardKernel(new WebModule());
}
}
}
</textarea>

Ninject is now set up. At any time during development you can switch your IRepository implementation by changing the bindings of the NinjectModule.

Mocking an IRepository with Ninject

Because our Repository implementation uses some sort of database, we don't want our Unit Tests to depend on it. It is generally recommended to test persistence layers in separate integration tests.

For our Unit Tests, we set up a fake IRepository implementation with Moq. The Ninject.Moq plugin integrates our mock in our controller by using a MockingKernel (as opposed to the StandardKernel previously seen in our Global.asax).

Let's create a test context in which we set up the mocked injection. Notice how the kernel injects a mock in the controller and how we're then able to retrieve the mock to set it up.

C#
<textarea name="code" class="c#" cols="60" rows="10">
public class AthletesControllerContext
{
protected readonly MockingKernel kernel;
protected readonly AthletesController controller;
protected readonly Mock<irepository> repositoryMock;

public AthletesControllerContext()
{
kernel = new MockingKernel();
controller = new AthletesController();
//inject a mock of IRepository in the controller 
kernel.Inject(controller);
repositoryMock = Mock.Get(controller.Repository);

//setup mocks
Athlete mockAthlete = new Athlete()
{
BirthDate = new DateTime(1980, 5, 26),
Discipline = new Discipline()
{
Name = "curling",
IsPlayedInTeams = true
},
Country = new Country() { Name = "Luxemburg" },
FirstName = "tee",
LastName = "bot"
};
repositoryMock.Setup(m => m.Athletes).Returns(new[] { mockAthlete });
repositoryMock.Setup(m => m.GetAthletesByCountry("Luxemburg")).Returns(new[] {
    mockAthlete });
repositoryMock.Setup(m => m.GetAthletesByDiscipline("curling")).Returns(new[] {
    mockAthlete });
repositoryMock.Setup(m => m.GetAthleteByName("teebot")).Returns(mockAthlete);
}
}
</textarea>

[Thanks to Miguel Madero for helping me figure out this last part]

Our test class inherits from this context and our test methods mimic the behavior of the application, goal achieved.

C#
<textarea name="code" class="c#" cols="60" rows="10">
[TestClass]
public class AthletesControllerTest : AthletesControllerContext
{
[TestMethod]
public void Index_Returns_NotNull_ViewResult_With_Data()
{
//Act
ViewResult res = controller.Index() as ViewResult;

//Assert
Assert.IsNotNull(res.ViewData);
}

[TestMethod]
public void Detail_Returns_ViewResult_With_Data()
{
//Act
ViewResult res = controller.Detail("teebot") as ViewResult;

//Assert
Assert.IsNotNull(res.ViewData);
Assert.IsInstanceOfType(res.ViewData.Model, typeof(Athlete));
}

[TestMethod]
public void Detail_Returns_NotFound_View_For_NonExisting()
{
//Act
ViewResult res = controller.Detail("nonexisting") as ViewResult;

//Assert
Assert.AreEqual("NotFound", res.ViewName);
}
}
</textarea>

Wrapping Up

In this post we saw how to inject our Repository implementation as an IRepository member of the controller.

Finally we switched our implementation for a mocked Repository in our tests.

We clearly saw a case for injection as our application is now loosely coupled and our MVC is testable. We could switch from a database to an XML file as persistence layer without affecting the client or the tests.

This article was originally posted at http://www.teebot.be/feeds/posts/full

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Logica
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --