Тестирование модуля IAuthenticationFilter в WebApi 2
Я пытаюсь использовать unit test базовый фильтр проверки подлинности, который я написал для проекта WebApi 2, но у меня возникли проблемы с издевательством над объектом HttpAuthenticationContext, необходимым для вызова OnAuthentication.
public override void OnAuthentication(HttpAuthenticationContext context)
{
base.OnAuthentication(context);
var authHeader = context.Request.Headers.Authorization;
... the rest of my code here
}
Строка в реализации, которую я пытаюсь настроить для насмешки, - это та, которая устанавливает переменную authHeader.
Однако я не могу издеваться над объектом Headers, потому что он запечатан. И я не могу издеваться над запросом и устанавливать издеваемые заголовки, потому что это не виртуальное свойство. И так далее вверх по цепи вплоть до контекста.
Удалось ли кому-нибудь успешно протестировать новую реализацию IAuthenticationFilter?
Я использую Moq, но я уверен, что смогу следовать в любой насмешливой библиотеке, если у вас есть пример кода.
Спасибо за любую помощь.
Ответы
Ответ 1
Однако можно достичь того, чего вы хотели, поскольку ни один из объектов в контексте цепочки. 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. Используя описанный выше метод, вы можете протестировать свой фильтр, однако вы не можете проверить в тесте, были ли затронуты определенные свойства объектов выше, потому что они не могут быть переопределены - без этого нет возможности отследить это.
Ответ 2
Я смог использовать ответ от @mr100, чтобы начать работу над решением моей проблемы, которая была модульной проверкой нескольких реализаций IAuthorizationFilter. Чтобы эффективно разрешить авторизацию unit test web api, вы не можете использовать AuthorizationFilterAttribute, и вам нужно применить глобальный фильтр, который проверяет наличие пассивных атрибутов на контроллерах/действиях. Короче говоря, я расширил ответ от @mr100, чтобы включить mocks для дескрипторов контроллера/действия, которые позволяют вам тестировать с/без наличия ваших атрибутов. В качестве примера я включу более простой из двух фильтров, необходимых мне для unit test, который заставляет HTTPS-соединения для определенных контроллеров/действий (или глобально, если вы хотите):
Это атрибут, который применяется там, где вы хотите заставить соединение HTTPS, обратите внимание, что он ничего не делает (он пассивен):
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class HttpsRequiredAttribute : Attribute
{
public HttpsRequiredAttribute () { }
}
Это фильтр, который на каждом запросе проверяет, присутствует ли этот атрибут, и если соединение связано с 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();
}
}
И, наконец, тест, подтверждающий его, возвращает статус 403, если https требуется, но не используется (используя много ответов @mr100 здесь):
[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);
}