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

前端 未结 3 676
悲&欢浪女
悲&欢浪女 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 20:52
    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:

    • https://github.com/alexanderbeletsky/trackyt.net/blob/master/src/Web/Infrastructure/TrackyServices.cs https://github.com/alexanderbeletsky/trackyt.net/blob/master/src/Web/Infrastructure/TrackyControllerFactory.cs https://github.com/alexanderbeletsky/trackyt.net/blob/master/src/Web/Controllers/LoginController.cs
    0 讨论(0)
  • 2021-01-29 21:05

    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
    0 讨论(0)
  • 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<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.

    0 讨论(0)
提交回复
热议问题