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

前端 未结 3 677
悲&欢浪女
悲&欢浪女 2021-01-29 20:04

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
3条回答
  •  醉话见心
    2021-01-29 21:09

    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().To();
      kernel.Bind().To();
    }        
    

    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();
        repo.Setup(d=> d.TryCreateUser(It.IsAny())).Returns(true);
        var controller = new HomeController(repo);
        var result = controller.CreateUser("test");
        Assert.IsNotNull(result);
        Assert.IsOfType(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.

提交回复
热议问题