#c# #unit-testing #nunit #moq #ihttpmodule
#c# #модульное тестирование #nunit #moq #ihttpmodule
Вопрос:
Я внедряю IHttpModule
и пытаюсь написать модульные тесты для него (используя NUnit и Moq). У меня возникла проблема с имитацией HttpApplication
зависимости для Init
метода:
void Init(HttpApplication context);
Обычно ASP.NET управляет HttpApplication
экземпляром и передает его Init
методу. В Init
методе пользовательский IHttpModule
подписывается на события, опубликованные HttpApplication
экземпляром (например, BeginRequest
и EndRequest
).
Мне нужен какой-то способ имитировать HttpApplication
, чтобы я мог вызывать события и проверять, работает ли моя реализация IHttpModule
обработчиков событий.
Я пытался создать макет HttpApplication
в своем тесте:
// Mock for the logging dependency
var mockLogger = new Mock<ILogger>();
// My attempt at mocking the HttpApplication
var mockApplication = new Mock<HttpApplication>();
// MyModule is my class that implements IHttpModule
var myModule = new MyModule(mockLogger.Object);
// Calling Init, which subscribes my event handlers to the HttpApplication events
myModule.Init(mockApplication.Object);
// Attempting to raise the begin and end request events
mockApplication.Raise(a => a.BeginRequest = null, EventArgs.Empty);
mockApplication.Raise(a => a.EndRequest = null, EventArgs.Empty);
// RequestTime is a long property that tracks the time it took (in miliseconds) for a
// request to be processed and is set in the event handler subscribed to EndRequest
Assert.Greater(myModule.RequestTime, 0);
… но это выдает следующее сообщение об ошибке:
Выражение не является присоединением или отсоединением события, или событие объявлено в классе, но не помечено как виртуальное.
Когда я изучил эту ошибку, я узнал, что Moq может имитировать только интерфейсы и виртуальные методы… Итак, как я могу имитировать конкретный класс, над которым у меня нет контроля?
Вот MyModule
класс:
public class MyModule : IHttpModule
{
ILogger _logger;
public long RequestTime { get; private set; }
Stopwatch _stopwatch;
public MyModule(ILogger logger)
{
_logger = logger;
}
public void Init(HttpApplication context)
{
context.BeginRequest = OnBeginRequest;
context.EndRequest = OnEndRequest;
}
public void Dispose() { }
void OnBeginRequest(object sender, EventArgs e)
{
_stopwatch = Stopwatch.StartNew();
}
void OnEndRequest(object sender, EventArgs e)
{
_stopwatch.Stop();
RequestTime = _stopwatch.ElapsedMilliseconds;
}
}
Комментарии:
1. Вы можете попробовать обойти это с помощью интерфейса.
2. @cdev Спасибо за ответ, но мне трудно понять, что вы имеете в виду. Можете ли вы объяснить это более подробно или на примере?
3. Переместите методы, используемые HttpApplication, в class и interface, и вы сможете имитировать ожидаемое поведение этого класса. На самом деле я предпочитаю интеграционное тестирование в сценариях, подобных этому.
4. @cdev Я все еще не понимаю, что вы имеете в виду.
HttpApplication
это класс от Microsoft вSystem.Web
. Я не могу заставить его наследовать новый интерфейс или изменятьсяHttpApplication
вообще.5. Интересно, никто не предложил написать интеграционные тесты. Модульные тесты имеют хорошую особенность — если вам трудно писать тесты. Архитектура не предназначена для модульных тестов, если вы не можете изменить дизайн и все еще хотите автоматически протестировать его — используйте интеграционные тесты, которые охватят весь конвейер вашего приложения.
Ответ №1:
Я не думаю, что обычно это рекомендуется, но вы можете использовать отражение, чтобы имитировать непубличных / виртуальных членов.
Для текущей реализации, например, так
public class MockHttpApplication : HttpApplication
{
public void RaiseBeginRequest()
{
FindEvent("EventBeginRequest").DynamicInvoke(this, EventArgs.Empty);
}
public void RaiseEndRequest()
{
FindEvent("EventEndRequest").DynamicInvoke(this, EventArgs.Empty);
}
private Delegate FindEvent(string name)
{
var key = typeof(HttpApplication)
.GetField(name, BindingFlags.Static | BindingFlags.NonPublic)
.GetValue(null);
var events = typeof(HttpApplication)
.GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(this) as EventHandlerList;
return events[key];
}
}
Комментарии:
1. Это заставляет модульный тест работать, проголосовал за. Можете ли вы немного объяснить код и подробнее рассказать о том, почему, по вашему мнению, это обычно не рекомендуется?
2. Глядя на внутреннюю реализацию
HttpApplication
, обработчики событийBeginRequest
иEndRequest
хранятся вEvents
свойстве с ключевыми объектами, которые определены как частное поле. Используя отражение, вы можете получить доступ к этим элементам и запустить событие вручную. Конечно, эти реализации не являются общедоступными API и, возможно, когда-нибудь будут изменены, поэтому я сказал, что это не рекомендуется.