Как правильнее организовать тестирование логики модулей (IHttpModule) и хендлеров (IHttpHandler) ASP.NET?
Как тестировать логику модулей и хендлеров ASP.NET?
Лучший ответ:
Парадигма ASP.NET не проектировалась для поддержки модульного тестирования. Так, типичный модуль или обработчик работает с экземплярами классов HttpContext
, HttpResponse
, HttpRequest
(и многими другими), которые являются синтглтонами. Соответственно тестировать такой код непосредственно практически невозможно. Для решения задачи тестирования создаются обертки над указанными синглтонами и вся логика строится так, чтобы работать с экземплярами интерфейсов, а не со статичными объектами. К счастью создавать библиотеки-обертки самому не нужно т.к. есть готовые варианты.
Вариант 1: Phil Haack описывает такую библиотеку-обертку в статье IHttpContext And Other Interfaces For Your Duck Typing Benefit
Вариант 2: В пространстве имен System.Web.Abstractions
есть реализованные обертки над объектами HttpContext
, HttpResponse
, HttpRequest и т.п. Это классы HttpContextWrapper
, HttpResponseWrapper
и HttpRequestWrapper
соответственно. Эти классы используются внутри ASP.NET MVC, именно для поддержки возможности тестирования. Есть интересная техника тестирования модулей и хендлеров, применяемая в частности в CMS Kigg. Суть подхода:
Создаются базовые классы, в которых объявлены абстрактные методы, использующие обертки вместо реальных http-related объектов:
public abstract class BaseHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += (sender, e) => OnBeginRequest(new HttpContextWrapper(((HttpApplication) sender).Context));
context.Error += (sender, e) => OnError(new HttpContextWrapper(((HttpApplication) sender).Context));
context.EndRequest += (sender, e) => OnEndRequest(new HttpContextWrapper(((HttpApplication) sender).Context));
}
public void Dispose() {}
public virtual void OnBeginRequest(HttpContextBase context){}
public virtual void OnError(HttpContextBase context){}
public virtual void OnEndRequest(HttpContextBase context){}
}
public abstract class BaseHttpHandler : IHttpHandler
{
public virtual bool IsReusable
{
get
{
return false;
}
}
public void ProcessRequest(HttpContext context)
{
ProcessRequest(new HttpContextWrapper(context));
}
public abstract void ProcessRequest(HttpContextBase context);
}
Конкретные реализации работают только с обертками, тесты также вызывают методы, принимающие обертки. Таким образом, в тестах легко можно создавать заглушки для всех проблемных объектов.