Pregunta

I have a simple Http module:

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()
        {
        }
    }

How can I unit test this? Especially how is it possible to mock events? Can anyone give some simple example?

¿Fue útil?

Solución

Not sure why you have decided to hardwire the dependencies as new LogService() and new HttpContextWrapper(HttpContext.Current) within the CustomLoggingModule. If want to test whether LogInfo() method is called or not, it becomes lot easier if you can externalize these dependencies so you can inject stubbed/mocked version etc.

Also your question does not state that you are using an IOC container. You can register the HttpModule with the container and provide external dependencies at runtime. Your question also does not state that using an isoloation/mock object framework. Therefore I will provide you with a solution that you can verify whether LogInfo method is called, using hand written stubs and mocks.

To achieve this, we need to refactor CustomLoggingModule a bit, so it becomes more testable.

System Under Test (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(){ }
}

As you see above, I have introduced 2 additional properties - ILogService so I can provide a Mocked verion and a delegate Func which allows me to stub the new 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; }
}

And then your real ILogService

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

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

Unit Test :

You would create a MockLoggerService, so you can verify the interaction i,e whether the LogInfo() method was called, etc. You also need a stubbed LoggingHttpContextWrapper to provide the fake HttpContextWrapper to the SUT (System Under Test)/ CustomLoggingModule.

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 is very important. It is not the real logger service, but it is the mocked version. When we do public class MockLoggerService : ILogService this means that we are providing another layer of indirection to the logger service so we can verify the interaction of the behaviour.

You also notice that I have provided a boolean variable to verify whether the LogInfo method is called or not. This allows me to call this method from the SUT, and verify whether the method being called or not.

Now Your Unit Test can be implemented as below.

    [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);
    }

Otros consejos

I had the same issue with my custom http module and decided I won't give up that easily and will do all I can to trigger the BeginRequest event in unit test. I had to actually read through the source code of HttpApplication class and use reflection to invoke the method.

[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);
        }
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top