我有一个简单的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()
{
}
}
我如何对此进行单元测试?特别是如何模拟事件?谁能举一些简单的例子呢?
答案 0 :(得分:5)
不确定为什么你决定在 CustomLoggingModule 中将依赖项硬连接为 new LogService()和 new HttpContextWrapper(HttpContext.Current) 。如果想要测试是否调用了LogInfo()方法,那么如果可以外部化这些依赖项以便可以注入存根/模拟版本等,则会变得更容易。
此外,您的问题并未声明您使用的是IOC container。您可以register the HttpModule with the container并在运行时提供外部依赖项。您的问题也没有说明使用isoloation/mock object framework。 因此,我将为您提供一个解决方案,您可以使用手写存根和模拟来验证是否调用了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(){ }
}
如上所述,我已经介绍了另外两个属性 - ILogService ,因此我可以提供一个Mocked版本和一个委托 Func ,它允许我存根 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; }
}
然后是真正的ILogService
public interface ILogService {
void LogInfo(HttpContextWrapper httpContextWrapper);
}
public class LogService : ILogService {
public void LogInfo(HttpContextWrapper httpContextWrapper)
{
//real logger implementation
}
}
单元测试:
您将创建一个MockLoggerService,因此您可以验证交互i,是否调用了LogInfo()方法等。您还需要一个存根的LoggingHttpContextWrapper来为SUT(System Under Test)/ 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非常重要。它不是真正的记录器服务,但它是模拟版本。当我们执行公共类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);
}
答案 1 :(得分: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);
}
}