2011-08-01 104 views
2

抛出错误,因为HttpContext的的我那叫一个控制器的HttpContext,如:测试我的控制器在.NET MVC

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

    private readonly AuthenticationService _authenticationService = new AuthenticationService(); 
} 

的HttpContext是AuthenticationService类电话:

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

    ... 
} 

在我的项目中,在我的实例控制器发生错误时,我会测试控制器private IPrincipal _user = HttpContext.Current.User;行:未将对象引用设置为对象的实例。

我需要测试我的控制器?

回答

3

您错过的主要问题是如何设计ASP.NET MVC项目进行测试的知识。

您应该设计您的控制器以使用依赖注入。也就是说,控制器不应使用AuthenticationService的具体实现,而应使用IAuthenticationService,其具体实现将在运行时提供。现在,当控制器被创建时,AuthenticationService也被创建。但在测试场景中,HttpContext为null,并且创建AuthenticationService失败,导致NullReference异常。如果您通过接口设计该接口,那么出于测试目的,您将向控制器提供假实现AuthenticationService,并且不会抛出异常。

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; 
    } 
} 

在测试场景中,你可以使用假货IAuthenticationService实施一些嘲讽库,例如moq。并通过嘲讽为它提供价值

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

var controller = new ApuradorController(mockAuthenticationService.Object); 

这次它不会抛出异常。

如果您不了解单元测试项目设计的原理,上面提到的信息是没有用的。为了快速入门,请阅读this链接。为了进一步阅读,有关asp.net mvc的地址簿,我会推荐那些由史蒂文桑德森。单元测试控制器设计的主要思想是,您应该有能力向控制器,假存储库,服务等提供假组件,并且仅保留单元测试的控制器部分。然后用这些假部件测试控制器迭代。单元测试意味着测试交互。如果交互是正确的,那么这些组件的真正实现将会是正确的。如果他们错了,测试失败。

+0

我做你的建议:var authenticationService = new Mock (); var controller = new ApuradorController(authenticationService);但我收到一个异常:参数类型'Moq.Mock '不能分配给参数类型'App.Services.IAuthenticationService' –

+0

我应该使用新的ApuradorController(authenticationService.Object); ? –

+0

是的,你应该这样做。另外,如果你想使用该模拟ApplicationService的User属性,你应该设置()它。请阅读moq documentation wiki中的Setup() - http://code.google.com/p/moq/wiki/QuickStart – archil

0

你将不得不嘲笑HttpContextBase使其工作。 Hanselman的article可能会帮助你。

1

这看起来不太合适,你在服务层添加一个依赖到System.Web命名空间。最好将用户名传递给你的服务层 - 可能在构造函数中,可能最好是在基类服务类的构造函数中使用,所以它可以在你所有的服务方法中访问。

abstract class BaseService 
{ 
    procteced IPrinciple _userName; 

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

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

    } 
} 

在控制器:

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

也许这样的 - 如果你将要访问之类的角色等等,你可能会发现创建围绕ASP.net一个小包装类从您的服务层实现接口来执行诸如访问角色/配置文件信息之类的成员类。

0

您可以连接一个HttpContext的(真品),并使用它:

// 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... 

我个人会去加倍努力,并添加另一个抽象层,并建立做某事。如IPrincipalAccessor,查看所有其他答案以获取更多详细信息。

1

正如其他人所说,为了单元测试你的控制器,你需要设计它们,你可以在运行时替换HTTP上下文(请求,响应等等),因为你不会有真正的HTTP单元测试时的上下文。

您需要注意的另一件事是,当您通过单元测试(比如说ApuradorController.Index())在您的控制器中调用一个动作时,您将不会自动获得与ASP.NET相同的执行管道MVC为您提供了一个运行时间,因此一些属于“正常”执行的事件不会被触发。例如,如果您在OnActionExecuting中执行了一些操作,那么当您在单元测试中调用ApuradorController.Index()时,该方法将不会自动触发。

测试控制器最初可能很难,因为它会迫使您改变您的编程方式。最终的结果是更好的代码,但到达那里可能会有点挑战。