C# 单元测试ASP.NETMVC Authorize属性以验证重定向到登录页面
这很可能是一个只需要另一双眼睛的例子。我肯定错过了什么,但我不明白为什么这种东西不能被测试。我基本上是试图通过将控制器标记为[Authorize]属性来确保未经身份验证的用户无法访问视图,我试图使用以下代码对此进行测试:C# 单元测试ASP.NETMVC Authorize属性以验证重定向到登录页面,c#,asp.net-mvc,C#,Asp.net Mvc,这很可能是一个只需要另一双眼睛的例子。我肯定错过了什么,但我不明白为什么这种东西不能被测试。我基本上是试图通过将控制器标记为[Authorize]属性来确保未经身份验证的用户无法访问视图,我试图使用以下代码对此进行测试: [Fact] public void ShouldRedirectToLoginForUnauthenticatedUsers() { var mockControllerContext = new Mock<ControllerContext>()
[Fact]
public void ShouldRedirectToLoginForUnauthenticatedUsers()
{
var mockControllerContext = new Mock<ControllerContext>()
{ DefaultValue = DefaultValue.Mock };
var controller = new MyAdminController()
{ControllerContext = mockControllerContext.Object};
mockControllerContext.Setup(c =>
c.HttpContext.Request.IsAuthenticated).Returns(false);
var result = controller.Index();
Assert.IsAssignableFrom<RedirectResult>(result);
}
感谢您的帮助…您的测试级别不正确。[Authorize]属性确保路由引擎永远不会为未经授权的用户调用该方法-重定向结果实际上来自路由,而不是来自控制器方法 好消息是——已经有了这方面的测试覆盖(作为MVC框架源代码的一部分),所以我想说您不必担心它;只要确保您的控制器方法在被调用时做了正确的事情,并相信框架不会在错误的情况下调用它 编辑:如果要验证单元测试中是否存在该属性,则需要使用反射检查控制器方法,如下所示。此示例将验证随MVC2一起安装的“New ASP.NET MVC 2 Project”演示中ChangePassword POST方法上是否存在Authorize属性
[TestFixture]
public class AccountControllerTests {
[Test]
public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute() {
var controller = new AccountController();
var type = controller.GetType();
var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method");
}
}
为什么不使用反射来查找控制器类和/或您正在测试的操作方法上的
[Authorize]
属性呢?假设框架确实确保该属性得到尊重,这将是最容易做到的事情 您可能在错误的级别进行测试,但这是一个有意义的测试。我的意思是,如果我用authorize(Roles=“Superhero”)属性标记一个方法,如果我标记它,我实际上不需要测试。我(认为我)想要的是测试未经授权的用户没有访问权限,而授权用户有访问权限
对于未经授权的用户,进行如下测试:
// Arrange
var user = SetupUser(isAuthenticated, roles);
var controller = SetupController(user);
// Act
SomeHelper.Invoke(controller => controller.MyAction());
// Assert
Assert.AreEqual(401,
controller.ControllerContext.HttpContext.Response.StatusCode, "Status Code");
嗯,这不容易,花了我10个小时,但在这里。我希望有人能从中受益,或者说服我从事另一种职业。:)(顺便说一句,我正在使用rhino mock)
但是,如果没有此帮助器功能,这不是很有用:
public static HttpContextBase FakeHttpContext(MockRepository mocks, bool isAuthenticated)
{
var context = mocks.StrictMock<HttpContextBase>();
var request = mocks.StrictMock<HttpRequestBase>();
var response = mocks.StrictMock<HttpResponseBase>();
var session = mocks.StrictMock<HttpSessionStateBase>();
var server = mocks.StrictMock<HttpServerUtilityBase>();
var cachePolicy = mocks.Stub<HttpCachePolicyBase>();
var user = mocks.StrictMock<IPrincipal>();
var identity = mocks.StrictMock<IIdentity>();
var itemDictionary = new Dictionary<object, object>();
identity.Expect(id => id.IsAuthenticated).Return(isAuthenticated);
user.Expect(u => u.Identity).Return(identity).Repeat.Any();
context.Expect(c => c.User).PropertyBehavior();
context.User = user;
context.Expect(ctx => ctx.Items).Return(itemDictionary).Repeat.Any();
context.Expect(ctx => ctx.Request).Return(request).Repeat.Any();
context.Expect(ctx => ctx.Response).Return(response).Repeat.Any();
context.Expect(ctx => ctx.Session).Return(session).Repeat.Any();
context.Expect(ctx => ctx.Server).Return(server).Repeat.Any();
response.Expect(r => r.Cache).Return(cachePolicy).Repeat.Any();
response.Expect(r => r.StatusCode).PropertyBehavior();
return context;
}
公共静态HttpContextBase FakeHttpContext(MockRepository mocks,bool已验证)
{
var context=mocks.StrictMock();
var request=mocks.StrictMock();
var response=mocks.StrictMock();
var session=mocks.StrictMock();
var server=mocks.StrictMock();
var cachePolicy=mocks.Stub();
var user=mocks.StrictMock();
var identity=mocks.StrictMock();
var itemDictionary=新字典();
identity.Expect(id=>id.IsAuthenticated).Return(IsAuthenticated);
Expect(u=>u.Identity).Return(Identity.Repeat.Any();
Expect(c=>c.User).PropertyBehavior();
context.User=User;
Expect(ctx=>ctx.Items).Return(itemDictionary).Repeat.Any();
Expect(ctx=>ctx.Request).Return(Request).Repeat.Any();
Expect(ctx=>ctx.Response).Return(Response.Repeat.Any();
Expect(ctx=>ctx.Session).Return(Session).Repeat.Any();
Expect(ctx=>ctx.Server).Return(Server.Repeat.Any();
Expect(r=>r.Cache).Return(cachePolicy).Repeat.Any();
Expect(r=>r.StatusCode).PropertyBehavior();
返回上下文;
}
因此,您可以确认不在角色中的用户没有访问权限。我试着写一个测试来证实相反的情况,但在mvc管道中挖掘了两个多小时后,我将把它留给手动测试人员。(当我进入VirtualPath ProviderViewEngine类时,我退出了。WTF?我不想做任何事,比如说VirtualPath、Provider或ViewEngine——这三者的结合!)
我很好奇,在一个所谓的“可测试”框架中,这为什么如此困难 我不同意Dylan的回答,因为“用户必须登录”并不意味着“控制器方法用AuthorizeAttribute注释” 为了确保在调用action方法时“用户必须登录”,ASP.NET MVC框架会执行类似的操作(请稍候,它最终会变得更简单) 在工作的c代码中很难实现这个伪脚本。很可能,这使得设置这样的测试变得非常简单,并在幕后执行这些步骤。这里有一个例子 首先从nuget安装软件包(撰写本文时版本为1.4.0-beta4) PM>安装包Xania.AspNet.Simulator-预安装 然后,您的测试方法可能如下所示(假设安装了NUnit和FluentAssertions):
对于.NET Framework,我们使用此类来验证每个MVC和API控制器是否具有
AuthorizeAttribute
,以及每个API控制器是否应该具有RoutePrefixAttribute
[TestFixture]
public class TestControllerHasAuthorizeRole
{
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
[Test]
public void MvcControllersShouldHaveAuthrorizeAttribute()
{
var controllers = GetChildTypes<Controller>();
foreach (var controller in controllers)
{
var authorizeAttribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Mvc.AuthorizeAttribute), true) as System.Web.Mvc.AuthorizeAttribute;
Assert.IsNotNull(authorizeAttribute, $"MVC-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.AuthorizeAttribute), true) as System.Web.Http.AuthorizeAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveRoutePrefixAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.RoutePrefixAttribute), true) as System.Web.Http.RoutePrefixAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement RoutePrefixAttribute");
Assert.IsTrue(attribute.Prefix.StartsWith("api/", StringComparison.OrdinalIgnoreCase), $"API-controller {controller.FullName} does not have a route prefix that starts with api/");
}
}
}
谢谢Dylan-我想我可能测试错了。我很高兴“假设”如果控制器被击中,用户就被验证。另外,你确定它已经在框架中测试过了吗?我可以看到一些测试提供有效的IPrincipal,但没有一个测试无效的情况;-)呃,不。。。我自己还没有真正验证过这个测试用例;我相信MVC团队会做对的。我的错!我喜欢这个答案,因为它不是正确的方法,但我不相信“该特性在框架中测试并工作”这一论点。我相信该属性工作正常,这是框架的工作,但我仍然希望声明我的控制器的哪些方法使用该属性。@Mathias-有关如何使用反射来验证所需属性的存在的示例,请参见编辑。
the[Authorize]属性确保路由引擎不会为未经授权的用户调用该方法-重定向结果实际上来自路由,而不是来自控制器方法。
-这句话的所有内容都是错误的。路由发生在身份验证之前很久
public static HttpContextBase FakeHttpContext(MockRepository mocks, bool isAuthenticated)
{
var context = mocks.StrictMock<HttpContextBase>();
var request = mocks.StrictMock<HttpRequestBase>();
var response = mocks.StrictMock<HttpResponseBase>();
var session = mocks.StrictMock<HttpSessionStateBase>();
var server = mocks.StrictMock<HttpServerUtilityBase>();
var cachePolicy = mocks.Stub<HttpCachePolicyBase>();
var user = mocks.StrictMock<IPrincipal>();
var identity = mocks.StrictMock<IIdentity>();
var itemDictionary = new Dictionary<object, object>();
identity.Expect(id => id.IsAuthenticated).Return(isAuthenticated);
user.Expect(u => u.Identity).Return(identity).Repeat.Any();
context.Expect(c => c.User).PropertyBehavior();
context.User = user;
context.Expect(ctx => ctx.Items).Return(itemDictionary).Repeat.Any();
context.Expect(ctx => ctx.Request).Return(request).Repeat.Any();
context.Expect(ctx => ctx.Response).Return(response).Repeat.Any();
context.Expect(ctx => ctx.Session).Return(session).Repeat.Any();
context.Expect(ctx => ctx.Server).Return(server).Repeat.Any();
response.Expect(r => r.Cache).Return(cachePolicy).Repeat.Any();
response.Expect(r => r.StatusCode).PropertyBehavior();
return context;
}
let $filters = All associated filter attributes which implement
IAuthorizationFilter
let $invoker = instance of type ControllerActionInvoker
let $ctrlCtx = instance or mock of type ControllerContext
let $actionDesc = instance or mock of type ActionDescriptor
let $authzCtx = $invoker.InvokeAuthorizationFilters($ctrlCtx, $filters, $actionDesc);
then controller action is authorized when $authzCtx.Result is not null
[Test]
public void AnonymousUserIsNotAuthorized()
{
// arrange
var action = new ProfileController().Action(c => c.Index());
// act
var result = action.GetAuthorizationResult();
// assert
result.Should().NotBeNull();
}
[Test]
public void LoggedInUserIsAuthorized()
{
// arrange
var action = new ProfileController().Action(c => c.Index())
// simulate authenticated user
.Authenticate("user1", new []{"role1"});
// act
var result = action.GetAuthorizationResult();
// assert
result.Should().BeNull();
}
[TestFixture]
public class TestControllerHasAuthorizeRole
{
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
[Test]
public void MvcControllersShouldHaveAuthrorizeAttribute()
{
var controllers = GetChildTypes<Controller>();
foreach (var controller in controllers)
{
var authorizeAttribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Mvc.AuthorizeAttribute), true) as System.Web.Mvc.AuthorizeAttribute;
Assert.IsNotNull(authorizeAttribute, $"MVC-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.AuthorizeAttribute), true) as System.Web.Http.AuthorizeAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveRoutePrefixAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.RoutePrefixAttribute), true) as System.Web.Http.RoutePrefixAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement RoutePrefixAttribute");
Assert.IsTrue(attribute.Prefix.StartsWith("api/", StringComparison.OrdinalIgnoreCase), $"API-controller {controller.FullName} does not have a route prefix that starts with api/");
}
}
}
public class AuthorizeAttributeTest
{
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
[Fact]
public void ApiAndMVCControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ControllerBase>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(Microsoft.AspNetCore.Authorization.AuthorizeAttribute), true) as Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
Assert.NotNull(attribute);
}
}
}