C# WebApi 2中的单元测试IAAuthenticationFilter
我正在尝试对我为WebApi 2项目编写的基本身份验证过滤器进行单元测试,但在模拟OnAuthentication调用中所需的HttpAuthenticationContext对象时遇到问题C# WebApi 2中的单元测试IAAuthenticationFilter,c#,unit-testing,asp.net-web-api,moq,C#,Unit Testing,Asp.net Web Api,Moq,我正在尝试对我为WebApi 2项目编写的基本身份验证过滤器进行单元测试,但在模拟OnAuthentication调用中所需的HttpAuthenticationContext对象时遇到问题 public override void OnAuthentication(HttpAuthenticationContext context) { base.OnAuthentication(context); var authHeader = context.Request.Heade
public override void OnAuthentication(HttpAuthenticationContext context)
{
base.OnAuthentication(context);
var authHeader = context.Request.Headers.Authorization;
... the rest of my code here
}
我试图为模拟设置的实现中的行是设置authHeader变量的行
但是,我不能模拟Headers对象,因为它是密封的。我不能模拟请求并设置模拟头,因为它是非虚拟属性。等等,一直到上下文
是否有人成功地对新的IAAuthenticationFilter实现进行了单元测试
我正在使用Moq,但我相信,如果您有示例代码,我可以在任何模拟库中使用Moq
感谢您的帮助。可以实现您想要的功能,但是链context.Request.Headers.Authorization中的任何对象都不会公开虚拟属性Mock或任何其他框架不会为您提供太多帮助。以下是使用模拟值获取HttpAuthenticationContext的代码:
HttpRequestMessage request = new HttpRequestMessage();
HttpControllerContext controllerContext = new HttpControllerContext();
controllerContext.Request = request;
HttpActionContext context = new HttpActionContext();
context.ControllerContext = controllerContext;
HttpAuthenticationContext m = new HttpAuthenticationContext(context, null);
HttpRequestHeaders headers = request.Headers;
AuthenticationHeaderValue authorization = new AuthenticationHeaderValue("scheme");
headers.Authorization = authorization;
您只需要以普通方式创建某些对象,并使用构造函数或属性将它们传递给其他对象。我之所以创建HttpControllerContext和HttpActionContext实例,是因为HttpAuthenticationContext.Request属性只有get部分-其值可以通过HttpControllerContext设置。使用上述方法,您可以测试过滤器,但是,您无法在测试中验证上面的对象的某些属性是否仅仅是因为它们不可重写,否则就不可能跟踪它。我能够使用@mr100的答案开始解决我的问题,这是对几个IAuthorizationFilter进行单元测试实现。为了有效地对web api授权进行单元测试,您不能真正使用AuthorizationFilterAttribute,您必须应用全局筛选器来检查控制器/操作上是否存在被动属性。长话短说,我对@mr100的答案进行了扩展,包括控制器/动作描述符的模拟,这些描述符允许您在属性存在/不存在的情况下进行测试。作为示例,我将包括我需要进行单元测试的两个过滤器中较简单的一个,这两个过滤器为指定的控制器/操作强制HTTPS连接(如果需要,也可以全局): 这是应用于任何您想要强制HTTPS连接的地方的属性,请注意,它不做任何事情(它是被动的): 这是一个过滤器,用于在每次请求时检查属性是否存在,以及连接是否通过HTTPS:
public class HttpsFilter : IAuthorizationFilter
{
public bool AllowMultiple => false;
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
List<HttpsRequiredAttribute> action = actionContext.ActionDescriptor.GetCustomAttributes<HttpsRequiredAttribute>().ToList();
List<HttpsRequiredAttribute> controller = actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<HttpsRequiredAttribute>().ToList();
// if neither the controller or action have the HttpsRequiredAttribute then don't bother checking if connection is HTTPS
if (!action.Any() && !controller.Any())
return continuation();
// if HTTPS is required but the connection is not HTTPS return a 403 forbidden
if (!string.Equals(actionContext.Request.RequestUri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
return Task.Factory.StartNew(() => new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Https Required",
Content = new StringContent("Https Required")
});
}
return continuation();
}
}
公共类HttpsFilter:IAuthorizationFilter
{
public bool AllowMultiple=>false;
公共任务ExecuteAuthorizationFilterAsync(HttpActionContext actionContext、CancellationToken CancellationToken、Func continuation)
{
List action=actionContext.ActionDescriptor.GetCustomAttributes().ToList();
列表控制器=actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes().ToList();
//如果控制器或操作都没有HttpsRequiredAttribute,那么不要麻烦检查连接是否为HTTPS
如果(!action.Any()&&!controller.Any())
返回continuation();
//如果需要HTTPS,但连接不是HTTPS,则返回403禁止
if(!string.Equals(actionContext.Request.RequestUri.Scheme,“https”,StringComparison.OrdinalIgnoreCase))
{
返回Task.Factory.StartNew(()=>新的HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase=“需要Https”,
内容=新的StringContent(“需要Https”)
});
}
返回continuation();
}
}
最后一个测试证明,当需要https但未使用https时,它返回403禁止状态(这里使用了大量@mr100的答案):
[TestMethod]
公共无效HttpsFilter\u禁止403\u与HTTPWhenHttpSisrequiredbyAction()
{
HttpRequestMessage requestMessage=新建HttpRequestMessage();
SetRequestContext(新的HttpRequestContext());
requestMessage.RequestUri=新Uri(“http://www.some-uri.com“”;//注意此处的http(不是https)
HttpControllerContext controllerContext=新的HttpControllerContext();
controllerContext.Request=requestMessage;
Mock controllerDescriptor=new Mock();
controllerDescriptor.Setup(m=>m.GetCustomAttributes())。返回(新集合());//控制器的空集合
Mock actionDescriptor=new Mock();
actionDescriptor.Setup(m=>m.GetCustomAttributes())。返回(新集合(){new HttpsRequiredAttribute()});//集合有一个用于操作的属性
actionDescriptor.Object.ControllerDescriptor=ControllerDescriptor.Object;
HttpActionContext actionContext=新的HttpActionContext();
actionContext.ControllerContext=ControllerContext;
actionContext.ActionDescriptor=ActionDescriptor.Object;
HttpAuthenticationContext authContext=新的HttpAuthenticationContext(actionContext,null);
Func continuation=()=>Task.Factory.StartNew(()=>newhttpresponsemessage(){StatusCode=HttpStatusCode.OK});
HttpsFilter filter=新的HttpsFilter();
HttpResponseMessage response=filter.ExecuteAuthorizationFilterAsync(actionContext,new CancellationTokenSource().Token,continuation).Result;
Assert.AreEqual(HttpStatusCode.Forbidden、response.StatusCode);
}
如果这不成功,那只是一个建议。您可以在单元测试中创建owin服务器,并使用伪造的用户身份验证令牌向控制器操作发出请求,并查看用户是否通过授权过滤器。不过,如果您只测试这个过滤器,那就有点麻烦了。
public class HttpsFilter : IAuthorizationFilter
{
public bool AllowMultiple => false;
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
List<HttpsRequiredAttribute> action = actionContext.ActionDescriptor.GetCustomAttributes<HttpsRequiredAttribute>().ToList();
List<HttpsRequiredAttribute> controller = actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<HttpsRequiredAttribute>().ToList();
// if neither the controller or action have the HttpsRequiredAttribute then don't bother checking if connection is HTTPS
if (!action.Any() && !controller.Any())
return continuation();
// if HTTPS is required but the connection is not HTTPS return a 403 forbidden
if (!string.Equals(actionContext.Request.RequestUri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
return Task.Factory.StartNew(() => new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Https Required",
Content = new StringContent("Https Required")
});
}
return continuation();
}
}
[TestMethod]
public void HttpsFilter_Forbidden403_WithHttpWhenHttpsIsRequiredByAction()
{
HttpRequestMessage requestMessage = new HttpRequestMessage();
requestMessage.SetRequestContext(new HttpRequestContext());
requestMessage.RequestUri = new Uri("http://www.some-uri.com"); // note the http here (not https)
HttpControllerContext controllerContext = new HttpControllerContext();
controllerContext.Request = requestMessage;
Mock<HttpControllerDescriptor> controllerDescriptor = new Mock<HttpControllerDescriptor>();
controllerDescriptor.Setup(m => m.GetCustomAttributes<HttpsRequiredAttribute>()).Returns(new Collection<HttpsRequiredAttribute>()); // empty collection for controller
Mock<HttpActionDescriptor> actionDescriptor = new Mock<HttpActionDescriptor>();
actionDescriptor.Setup(m => m.GetCustomAttributes<HttpsRequiredAttribute>()).Returns(new Collection<HttpsRequiredAttribute>() { new HttpsRequiredAttribute() }); // collection has one attribute for action
actionDescriptor.Object.ControllerDescriptor = controllerDescriptor.Object;
HttpActionContext actionContext = new HttpActionContext();
actionContext.ControllerContext = controllerContext;
actionContext.ActionDescriptor = actionDescriptor.Object;
HttpAuthenticationContext authContext = new HttpAuthenticationContext(actionContext, null);
Func<Task<HttpResponseMessage>> continuation = () => Task.Factory.StartNew(() => new HttpResponseMessage() { StatusCode = HttpStatusCode.OK });
HttpsFilter filter = new HttpsFilter();
HttpResponseMessage response = filter.ExecuteAuthorizationFilterAsync(actionContext, new CancellationTokenSource().Token, continuation).Result;
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
}