开发者

Testing My Controller throw error because of HttpContext in .net MVC

开发者 https://www.devze.com 2023-03-24 01:40 出处:网络
I have a开发者_高级运维 Controller that call HttpContext like: [Authorize(Roles = \"Administrador\")]

I have a开发者_高级运维 Controller that call HttpContext like:

[Authorize(Roles = "Administrador")]
public class ApuradorController : Controller
{
    private readonly Questiona2011Context _context = new Questiona2011Context();

    private readonly AuthenticationService _authenticationService = new AuthenticationService();
}

The HttpContext is call in AuthenticationService class:

public class AuthenticationService
{
    private IPrincipal _user = HttpContext.Current.User;

    ...
}

In my project where im test the controllers when I Instance the Controller a error is thrown in private IPrincipal _user = HttpContext.Current.User; Line: Object reference not set to an instance of an object.

What do I need to test my controllers?


The main thing that you miss is the knowledge of how to design ASP.NET MVC project for testing.

You should design your controller to use dependency injection. That is, controllers should not be using concrete implementation of AuthenticationService, but using IAuthenticationService, concrete implementation of which will be supplied in runtime. For now, when the controller is created, the AuthenticationService is also created. But in test scenario, HttpContext is null and Creating AuthenticationService is failing with NullReference exception. If you design that via interface, in testing purposes you will supply fake implementation of AuthenticationService to controller, and it will not throw exception.

public interface IAuthenticationService
{
    IPrincipal User {get;}
}

public class AuthenticationService : IAuthenticationService
{
    private IPrincipal _user = HttpContext.Current.User;

    ...
}

//the controller
[Authorize(Roles = "Administrador")]
public class ApuradorController : Controller
{
    private readonly Questiona2011Context _context = new Questiona2011Context();

    private readonly IAuthenticationService _authenticationService;

    public ApuradorController(IAuthenticationService authenticationService)
    {
         _authenticationService = authenticationService;
    }
}

In test scenario, you could use some mocking library for fake IAuthenticationService implementation, for example moq. And supply value for it via mocking

var mockAuthenticationService = new Mock<IAuthenticationService>();
//setup mockAuthenticationService

var controller = new ApuradorController(mockAuthenticationService.Object);

This time it will not throw exceptions.

The information mentioned above is not helpful if you do not understand principles of unit testable project design. For the quick start, read this link. For the further reading, address books about asp.net mvc, i would reccommend those by steven sanderson. The main idea of unit testable controller design is that you should have the ability to supply fake components to the controller, fake repositories, services, etc and leave real only the part of controller that is unit tested. Then test controller iteractions with those fake parts. The unit testing means testing that interactions. If interactions are correct, they will be correct with real implementations of those components. If they're wrong, test fails.


This doesn't look quite right to me, you're adding a dependency in your service layer to the System.Web namespace. Better to pass the username through to you service layer - possibly in the constructor and possibly best to have that in a constructor in a Base service class, so it can be accessible in all your service methods.

abstract class BaseService
{
    procteced IPrinciple _userName;

    public BaseService(IPrinciple userName)
    {
        _userName = userName;
     }
}

class AuthenticationService : BaseService
{
   public AuthenticationService(IPrinciple userName)
        :base(userName)
   {

   }
}

In controller:

AuthenticationService _service = new AuthenticationService(HttpContext.Current.User);

Possibly like that - if you are going to be accessing things like roles etc., you may find it useful to create a little wrapper class around the ASP.net membership classes that implements an interface from your service layer to do things like accessing roles/profile information.


As others have said in order to unit test your controllers you need to design them in ways that you can replace, at runtime, the HTTP context (request, response, et cetera) since you won't have a real HTTP context when unit testing.

The other thing that you need to be aware of is that when you invoke an action in your controller via a unit test (say ApuradorController.Index() ) you won't automatically get the same execution pipeline that ASP.NET MVC gives you a runtime and therefore some of the events that are part of the "normal" execution won't be triggered. For example, if you perform some actions in the OnActionExecuting that method won't be automatically triggered when you call ApuradorController.Index() in your unit test.

Testing controllers can be hard initially since it will force you to change the way you program. The net result is better code, but it might be a bit of a challenge to get there.


You are gonna have to mock the HttpContextBase to make this working. Hanselman's article on that might help you.


You can wire up a HttpContext (the real thing) and use it:

  // Arrange
  HttpContext.Current =
    new HttpContext(
      new HttpRequest( "", "http://tempuri.org", "" ),
      new HttpResponse( new StringWriter() ) );
  HttpContext.Current.User = new GenericPrincipal( new GenericIdentity("MyUser"), new[]{"Admin"} );

  // Call your controller action...

I personally would go the extra mile and add another layer of abstraction and build sth. like a IPrincipalAccessor, see all the other answers for more detail.

0

精彩评论

暂无评论...
验证码 换一张
取 消