Unit testing 如何在HttpModule中模拟事件

Unit testing 如何在HttpModule中模拟事件,unit-testing,mocking,httpmodule,Unit Testing,Mocking,Httpmodule,我有一个简单的Http模块: public class CustomLoggingModule : IHttpModule { public void Init(HttpApplication context) { context.BeginRequest += BeginRequest; context.EndRequest += EndRequest; } public

我有一个简单的Http模块:

public class CustomLoggingModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.BeginRequest += BeginRequest;

            context.EndRequest += EndRequest;
        }

        public void BeginRequest(object sender, EventArgs eventArgs)
        {
            //some code
        }

        public void EndRequest(object sender, EventArgs eventArgs)
        {
           //some
        }

        public void Dispose()
        {
        }
    }

如何进行单元测试?尤其是如何模拟事件?有人能举一些简单的例子吗?

不知道为什么您决定在CustomLoggingModule中将依赖项硬连接为new LogService()和new HttpContextWrapper(HttpContext.Current)。如果您想测试是否调用了LogInfo()方法,那么如果您可以将这些依赖项外部化,从而可以注入存根/模拟版本等,就变得容易多了

此外,您的问题没有说明您正在使用。您可以在运行时创建和提供外部依赖项。您的问题也没有说明使用。 因此,我将为您提供一个解决方案,您可以使用手写存根和模拟来验证是否调用了LogInfo方法

为了实现这一点,我们需要稍微重构CustomLoggingModule,使其更易于测试

被测系统(SUT)

然后是你真正的ILogService

public interface ILogService {
   void LogInfo(HttpContextWrapper httpContextWrapper);
}

public class LogService : ILogService {
   public void LogInfo(HttpContextWrapper httpContextWrapper)
   {
        //real logger implementation    
   }
}
单元测试:

您将创建一个MockLoggerService,以便验证交互,即是否调用了LogInfo()方法等。您还需要一个存根LoggingHttpContextWrapper来向SUT(测试中的系统)/CustomLoggingModule提供假HttpContextWrapper

public class StubLoggingHttpContextWrapper : ILoggingHttpContextWrapper
{
    public StubLoggingHttpContextWrapper(){}

    public HttpContextWrapper HttpContextWrapper { get; private set; }
}

public class MockLoggerService : ILogService
{
    public bool LogInfoMethodIsCalled = false;
    public void LogInfo(HttpContextWrapper httpContextWrapper) {
        LogInfoMethodIsCalled = true;
    }
}
模拟日志服务非常重要。它不是真正的记录器服务,但它是模拟版本。当我们执行公共类MockLoggerService:ILogService时,这意味着我们正在为记录器服务提供另一层间接寻址,以便我们可以验证行为的交互

您还注意到,我提供了一个布尔变量来验证是否调用了LogInfo方法。这允许我从SUT调用此方法,并验证是否调用该方法

现在,您的单元测试可以如下实现

    [TestMethod]
    public void CustomLoggingModule_BeginRequest_VerifyLogInfoMethodIsCalled()
    {
        var sut = new CustomLoggingModule();
        var loggerServiceMock = new MockLoggerService();
        var loggingHttpContextWrapperStub = new StubLoggingHttpContextWrapper();

        sut.LogService = loggerServiceMock;
        sut.LogginHttpContextWrapperDelegate = () => loggingHttpContextWrapperStub;

        sut.BeginRequest(new object(), new EventArgs());

        Assert.IsTrue(loggerServiceMock.LogInfoMethodIsCalled);
    }

我的自定义http模块也有同样的问题,我决定不会轻易放弃,我会尽我所能在单元测试中触发BeginRequest事件。我必须仔细阅读HttpApplication类的源代码,并使用反射来调用该方法

[TestMethod]
    public void EventTriggered_DoesNotError()
    {
        using (var application = new HttpApplication())
        {
            var module = new CustomLoggingModule();
            module.Init(application);

            FireHttpApplicationEvent(application, "EventBeginRequest", this, EventArgs.Empty);
        }
    }

    private static void FireHttpApplicationEvent(object onMe, string invokeMe, params object[] args)
    {
        var objectType = onMe.GetType();

        object eventIndex = (object)objectType.GetField(invokeMe, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(onMe);
        EventHandlerList events = (EventHandlerList)objectType.GetField("_events", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(onMe);

        EventHandler handler = (EventHandler)events[eventIndex];

        Delegate[] delegates = handler.GetInvocationList();

        foreach (Delegate dlg in delegates)
        {
            dlg.Method.Invoke(dlg.Target, args);
        }
    }

不确定这里要测试什么。没有太多的行为,这是基础设施。我建议将您的HttpModule作为一个基础架构问题。将您认为需要单元测试的任何行为抽象到单独的类/服务中,并对该行为进行单元测试。您可以在其他类型的测试中介绍HttpModule,例如验收测试。@Raj:谢谢您的回复。事实上我同意你的看法。在“BeginRequest”和“EndRequest”方法中的处理程序中,我使用执行主作业的“LogService”。因此,我想测试在处理程序中是否调用了“LogService”方法。如何将LogService注入模块?与往常一样。下面是BeginRequest方法中的内容:
ILogService service=new LogService();LogInfo(新的HttpContextWrapper(HttpContext.Current))thx-更新了下面的答案。这是一个非常好的尝试,不幸的是当测试命中方法本身时,例如
EventBeginRequest(objectsender,EventArgs e)
发送者类型是测试类(在我的例子中
{ProxyV2ConverterHttpModule.Tests.XForwardedForRewriterTests}
)而不是
System.Web.HttpApplication
,所以从这一点上来说,一切都失败了,这仍然是一次很好的尝试!对我来说只要稍加修改就行了干杯
    [TestMethod]
    public void CustomLoggingModule_BeginRequest_VerifyLogInfoMethodIsCalled()
    {
        var sut = new CustomLoggingModule();
        var loggerServiceMock = new MockLoggerService();
        var loggingHttpContextWrapperStub = new StubLoggingHttpContextWrapper();

        sut.LogService = loggerServiceMock;
        sut.LogginHttpContextWrapperDelegate = () => loggingHttpContextWrapperStub;

        sut.BeginRequest(new object(), new EventArgs());

        Assert.IsTrue(loggerServiceMock.LogInfoMethodIsCalled);
    }
[TestMethod]
    public void EventTriggered_DoesNotError()
    {
        using (var application = new HttpApplication())
        {
            var module = new CustomLoggingModule();
            module.Init(application);

            FireHttpApplicationEvent(application, "EventBeginRequest", this, EventArgs.Empty);
        }
    }

    private static void FireHttpApplicationEvent(object onMe, string invokeMe, params object[] args)
    {
        var objectType = onMe.GetType();

        object eventIndex = (object)objectType.GetField(invokeMe, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(onMe);
        EventHandlerList events = (EventHandlerList)objectType.GetField("_events", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(onMe);

        EventHandler handler = (EventHandler)events[eventIndex];

        Delegate[] delegates = handler.GetInvocationList();

        foreach (Delegate dlg in delegates)
        {
            dlg.Method.Invoke(dlg.Target, args);
        }
    }