如果不控制代码,如何模拟作为具体类的依赖项?

时间:2019-03-30 01:45:18

标签: c# unit-testing nunit moq ihttpmodule

我正在实现IHttpModule,并试图为此编写单元测试(使用NUnit和Moq)。我在模拟Init方法的HttpApplication依赖项时遇到问题:

void Init(HttpApplication context);

通常,ASP.NET控制HttpApplication实例并将其传递给Init方法。在Init方法中,自定义IHttpModule订阅由HttpApplication实例发布的事件(例如BeginRequestEndRequest)。

我需要某种方法来模拟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 个答案:

答案 0 :(得分:1)

我认为通常不建议这样做,但是您可以使用Reflection模拟非公共/虚拟成员。

对于当前的implementation,就像这样

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