3

我有一个简单的 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()
        {
        }
    }

我怎样才能对此进行单元测试?特别是如何模拟事件?谁能举一些简单的例子?

4

2 回答 2

5

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

此外,您的问题并未说明您正在使用IOC 容器。您可以向容器注册 HttpModule并在运行时提供外部依赖项。您的问题也没有说明使用隔离/模拟对象框架。因此,我将为您提供一个解决方案,您可以使用手写存根和模拟来验证是否调用了 LogInfo 方法。

为了实现这一点,我们需要稍微重构一下CustomLoggingModule,让它变得更加可测试。

被测系统 (SUT)

public class CustomLoggingModule : IHttpModule
{
    public ILogService LogService { get; set; }
    public Func<ILoggingHttpContextWrapper> LogginHttpContextWrapperDelegate { get; set; }

    public void Init(HttpApplication context) {
        context.BeginRequest += BeginRequest;
        context.EndRequest += EndRequest;
    }

    public CustomLoggingModule() {
        LogginHttpContextWrapperDelegate = () => new LoggingHttpContextWrapper();
    }

    public void BeginRequest(object sender, EventArgs eventArgs) {
        LogService.LogInfo(LogginHttpContextWrapperDelegate().HttpContextWrapper);
    }

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

    public void Dispose(){ }
}

正如您在上面看到的,我引入了 2 个附加属性 - ILogService,因此我可以提供一个 Mocked 版本和一个委托Func,它允许我对 新的 HttpContextWrapper(HttpContext.Current)存根;

public interface ILoggingHttpContextWrapper {
    HttpContextWrapper HttpContextWrapper { get; }
}

public class LoggingHttpContextWrapper : ILoggingHttpContextWrapper
{
    public LoggingHttpContextWrapper() {
        HttpContextWrapper = new HttpContextWrapper(HttpContext.Current);
    }

    public HttpContextWrapper HttpContextWrapper { get; private set; }
}

然后是你真正的 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 非常重要。它不是真正的记录器服务,而是模拟版本。当我们执行public class 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);
    }
于 2013-10-31T12:20:14.613 回答
1

我的自定义 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);
        }
    }
于 2014-06-09T09:35:35.607 回答