MVC 3: How to learn how to test with NUnit, Ninject, and Moq?
Asked Answered
R

3

50

Short version of my questions:

  1. Can anyone point me toward some good, detailed sources from which I can learn how to implement testing in my MVC 3 application, using NUnit, Ninject 2, and Moq?
  2. Can anyone here help clarify for me how Controller-Repository decoupling, mocking, and dependency injection work together?

Longer version of my questions:

What I'm trying to do ...

I am currently beginning to create an MVC 3 application, which will use Entity Framework 4, with a database first approach. I want to do this right, so I am trying to design the classes, layers, etc., to be highly testable. But, I have little to no experience with unit testing or integration testing, other than an academic understanding of them.

After lots of research, I've settle on using

  • NUnit as my testing framework
  • Ninject 2 as my dependency injection framework
  • Moq as my mocking framework.

I know the topic of which framework is best, etc., could enter into this, but at this point I really don't know enough about any of it to form a solid opinion. So, I just decided to go with these free solutions which seem to be well liked and well maintained.

What I've learned so far ...

I've spent some time working through some of this stuff, reading resources such as:

From these resources, I've managed to workout the need for a Repository pattern, complete with repository interfaces, in order to decouple my controllers and my data access logic. I have written some of that into my application already, but I admit I am not clear as to the mechanics of the whole thing, and whether I am doing this decoupling in support of mocking, or dependency injection, or both. As such, I certainly wouldn't mind hearing from you guys about this too. Any clarity I can gain on this stuff will help me at this point.

Where things got muddy for me ...

I thought I was grasping this stuff pretty well until I started trying to wrap my head around Ninject, as described in Building Testable ASP.NET MVC Applications, cited above. Specifically, I got completely lost around the point in which the author begins describing the implementation of a Service layer, about half way into the document.

Anyway, I am now looking for more resources to study, in order to try to get various perspectives around this stuff until it begins to make sense to me.

Summarizing all of this, boiling it down to specific questions, I am wondering the following:

  1. Can anyone point me toward some good, detailed sources from which I can learn how to implement testing in my MVC 3 application, using NUnit, Ninject 2, and Moq?
  2. Can anyone here help clarify for me how Controller-Repository decoupling, mocking, and dependency injection work together?

EDIT:

I just discovered the Ninject official wiki on Github, so I'm going to start working through that to see if it starts clarifying things for me. But, I'm still very interested in the SO community thoughts on all of this :)

Rafaellle answered 11/7, 2011 at 5:49 Comment(0)
G
59

If you are using the Ninject.MVC3 nuget package, then some of the article you linked that was causing confusion will not be required. That package has everything you need to start injecting your controllers which is probably the biggest pain point.

Upon installing that package, it will create a NinjectMVC3.cs file in the App_Start folder, inside that class is a RegisterServices method. This is where you should create the bindings between your interfaces and your implementations

private static void RegisterServices(IKernel kernel)  
{  
  kernel.Bind<IRepository>().To<MyRepositoryImpl>();
  kernel.Bind<IWebData>().To<MyWebDAtaImpl>();
}        

Now in your controller you can use constructor injection.

public class HomeController : Controller {  
    private readonly IRepository _Repo;
    private readonly IWebData _WebData;

    public HomeController(IRepository repo, IWebData webData) {
      _Repo = repo;
      _WebData = webData;
    }
}

If you are after very high test coverage, then basically anytime one logical piece of code (say controller) needs to talk to another (say database) you should create an interface and implementation, add the definition binding to RegisterService and add a new constructor argument.

This applies not only to Controller, but any class, so in the example above if your repository implementation needed an instance of WebData for something, you would add the readonly field and the constructor to your repository implementation.

Then when it comes to testing, what you want to do is provide mocked version of all required interfaces, so that the only thing you are testing is the code in the method you are writing the test for. So in my example, say that IRepository has a

bool TryCreateUser(string username);

Which is called by a controller method

public ActionResult CreateUser(string username) {
    if (_Repo.TryCreateUser(username))
       return RedirectToAction("CreatedUser");
    else
       return RedirectToAction("Error");
}

What you are really trying to test here is that if statement and the return types, you do not want to have to create a real repository that will return true or false based on special values you give it. This is where you want to mock.

public void TestCreateUserSucceeds() {
    var repo = new Mock<IRepository>();
    repo.Setup(d=> d.TryCreateUser(It.IsAny<string>())).Returns(true);
    var controller = new HomeController(repo);
    var result = controller.CreateUser("test");
    Assert.IsNotNull(result);
    Assert.IsOfType<RedirectToActionResult>(result)
    Assert.AreEqual("CreatedUser", ((RedirectToActionResult)result).RouteData["Action"]);
}

^ That won't compile for you as I know xUnit better, and do not remember the property names on RedirectToActionResult from the top of my head.

So to sum up, if you want one piece of code to talk to another, whack an interface in between. This then allows you to mock the second piece of code so that when you test the first you can control the output and be sure you are testing only the code in question.
I think it was this point that really made the penny drop for me with all this, you do this not necessarily becase the code demands it, but because the testing demands it.

One last piece of advice specific to MVC, any time you need to access the basic web objects, HttpContext, HttpRequest etc, wrap all these behind an interface as well (like the IWebData in my example) because while you can mock these using the *Base classes, it becomes painful very quickly as they have a lot of internal dependencies you also need to mock.
Also with Moq, set the MockBehaviour to Strict when creating mocks and it will tell you if anything is being called that you have not provided a mock for.

Goahead answered 11/7, 2011 at 6:42 Comment(3)
Chris, this is a fantastic answer. Your descriptions and examples are clear and concise. You've told me more in one post than I've learned from 4 entire articles. Thank you! I do, however, have one (kind of dumb) question. Should I be installing Ninject inside my test project, or the project I'm testing, or both? I am only confused on this point because I read somewhere that, prior to the MVC3 extension, -all- controllers needed to be provided by the DI framework, which I assume means all controllers in my test project, but just want to make sure.Rafaellle
@campbell You should only need Ninject in your web project. It is possible to use it in your test project to inject new Mock<T>'s for you, but I prefer to do it by hand.Goahead
Chris, I want to thank you again for this fantastic reply. I've referred back to it often. I can't tell you how grateful I am for all of the time you've saved me with this single post.Rafaellle
C
9
  1. Here is the application that I'm creating. It is open source and available on github, and utilizes all of the required stuff - MVC3, NUnit, Moq, Ninject - https://github.com/alexanderbeletsky/trackyt.net/tree/master/src

  2. Contoller-Repository decoupling is simple. All data operations are moved toward the Repository. Repository is an implementation of some IRepository type. The controller never creates repositories inside itself (with the new operator) but rather receives them either by constructor argument or property.

.

public class HomeController {
  public HomeController (IUserRepository users) {

  }
}

This technique is called "Inversion of Control." To support inversion of control you have to provide some "Dependency Injection" framework. Ninject is a good one. Inside Ninject you associate some particular interface with an implementation class:

Bind<IUserRepository>().To<UserRepository>();

You also substitute the default controller factory with your custom one. Inside the custom one you delegate the call to the Ninject kernel:

public class TrackyControllerFactory : DefaultControllerFactory
{
    private IKernel _kernel = new StandardKernel(new TrackyServices());

    protected override IController GetControllerInstance(
        System.Web.Routing.RequestContext requestContext,
        Type controllerType)
    {
        if (controllerType == null)
        {
            return null;
        }

        return _kernel.Get(controllerType) as IController;
    }
}

When the MVC infrastructure is about to create a new controller, the call is delegated to the custom controller factory GetControllerInstance method, which delegates it to Ninject. Ninject sees that to create that controller the constructor has one argument of type IUserRepository. By using the declared binding, it sees that "I need to create a UserRepository to satisfy the IUserRepository need." It creates the instance and passes it to the constructor.

The constructor is never aware of what exact instance would be passed inside. It all depends on the binding you provide for that.

Code examples:

Christ answered 11/7, 2011 at 6:25 Comment(1)
Thanks, alexanderb. I've voted for your response due to the quality links to real-world examples that you've provided.Rafaellle
K
1

Check it out : DDD Melbourne video - New development workflow

The whole ASP.NET MVC 3 development process was very well presented.

The third party tools I like most are:

  • Using NuGet to install Ninject to enable DI throughout the MVC3 framework
  • Using NuGet to install nSubstite to create mocks to enable unit testing
Katabatic answered 13/7, 2011 at 1:59 Comment(1)
Vincent, the sound quality of that video was questionable, but the content was worth it.Rafaellle

© 2022 - 2024 — McMap. All rights reserved.