Как вы можете издеваться над зависимостью, которая является конкретным классом, если вы не управляете кодом? - PullRequest
0 голосов
/ 30 марта 2019

Я реализую 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 Ответ

1 голос
/ 30 марта 2019

Я не думаю, что это обычно рекомендуется, но вы можете использовать Reflection для насмешки не публичных / виртуальных участников.

Для текущей реализации , вот так

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];
    }
}

...