OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

ASP.NET HttpApplicationへの依存をスタブ化する

Visual C# .NET を使用して ASP.NET HTTP モジュールを作成する方法に、ASP.NETでhttpモジュールクラスを自作する方法が書かれている。

ポイントだけ書くと、以下のようになる。

  • IHttpModule インタフェースを実装する
  • HttpApplication から購読するイベントを選択
  • Init メソッド内で、そのイベントのハンドラを登録する
public void Init(HttpApplication app)
{
   app.BeginRequest += new EventHandler(OnBeginRequest);
}

ここで、Init メソッドで受け取る HttpApplication のインスタンスは、ASP.NETでマネージされているもの。
そして HttpApplication 内の BeginRequest などのイベントも ASP.NET 側から発生されるものになる。

困ったことに、IHttpModuleインタフェースがこの HttpApplication という具体クラスに依存しちまってるんで、テストどうしましょうね、という話。

単純にスタブ化、つまり、HttpApplication を継承したクラスを作って、BeginRequest 等のイベントを手動でコールする口を開けてやろうと思ってソースを見たら、ハンドラの持ち方がちょっとめんどくさい感じになってた。

HttpApplication の現在の実装

Events という、protectedなプロパティでハンドラのリストを持っている。

protected EventHandlerList Events {
    get {
        if (_events == null) {
            _events = new EventHandlerList();
        }
        return _events;
    }
}

実体はprivateフィールド。

private EventHandlerList _events;

この EventHandlerList は、オブジェクトをキーにしてイベントハンドラを保持するようなリストクラス。
で、キーとなるオブジェクトは HttpApplication 内に staticなフィールドとして定義されている。

// event handlers
private static readonly object EventDisposed = new object();
private static readonly object EventErrorRecorded = new object();
private static readonly object EventRequestCompleted = new object();
private static readonly object EventPreSendRequestHeaders = new object();
private static readonly object EventPreSendRequestContent = new object();
 
private static readonly object EventBeginRequest = new object();
private static readonly object EventAuthenticateRequest = new object();
private static readonly object EventDefaultAuthentication = new object();
private static readonly object EventPostAuthenticateRequest = new object();
...

なので、手動でイベントを発生させるためには、この Events から対象のイベントハンドラを取り出してファイヤーする感じになりますね。

ハンドラを取り出して実行する

HttpApplication を継承した DummyHttpApplication クラスを作ります。
その中に、BeginRequestイベントを発生させる公開メソッド RaiseBeginRequest を定義する。

public class DummyHttpApplication : HttpApplication
{
    public void RaiseBeginRequest()
    {
        //TODO 以下で書くよ
    }
}

Events プロパティ自体は protectedなので、サブクラスから普通に参照できる。

しかしキーになるオブジェクトは private なので、リフレクションを使って探す必要があります。

var keyField = typeof(HttpApplication)
                .GetField("EventBeginRequest", BindingFlags.Static | BindingFlags.NonPublic);
var key = keyField.GetValue(null);

リフレクションのラッパー・ライブラリ Merror を使ってくれてもよい。
その場合はこんな感じ。

var key = new Reflector(typeof(HttpApplication))
                .GetStaticField("EventBeginRequest");

EventHandlerList ではハンドラがDelegate型で保持されているので、EventHandlerにキャストしてから実行する。

var handler = this.Events[key] as EventHandler;
handler?.Invoke(this, EventArgs.Empty);

これを HttpApplication の代わりに渡してやれば、イベントを手動で発生させてハンドラ内の処理をテストすることができるようになります。

var app = new DummyHttpApplication();
var module = new MyModule();
module.Init(app);

app.RaiseBeginRequest();
// Assert something.

以上。