ユニットテストでセッションを使用する

前提

MVC(.net Framework)を使用したWebアプリケーションで、セッションのテストを行いたい。または、セッションを使用したいがどのようなコードを書けばよいのかわからない。
まずは下記3ファイルを作成しておく。

■View

@ModelType WebApplication1.DefaultModel
@Code
    ViewData("Title") = "Index"
End Code

<h2>Index</h2>
@Html.Raw(Model.session.SessionID)

■Model

Public Class DefaultModel
    Property session As HttpSessionStateBase
End Class

■Controller

Imports System.Web.Mvc

Namespace Controllers
    Public Class DefaultController
        Inherits Controller

        ' GET: Default
        Function Index() As ActionResult
            Dim model As New DefaultModel
            model.session = Session
            Return View(model)
        End Function

    End Class
End Namespace

※面倒であれば、スキャフォールディングアイテムで十分。上記も自動生成したものだ。

方法

1.NugetでMoqのパッケージをインストールする

※必ず、更新プログラムがないか確認すること。もとになっているプロジェクトが最新ではない場合、アセンブリのエラーが発生する。

2.コントトーラーとセッションの関係を理解する

そもそも、Sessionの所有者はだれなのか?セッションはだれが受け渡しを行っているのか。セッションの中身そのものは、Radisのような外部のサーバー、IIS(WorkerProcess)などのWebサーバー、ローカルのCookieなど様々。ただ、それをコントロールしているのは、文字通りController。

Contollerをのぞいてみると、下記のような読み取り専用のプロパティが設定されている。

        '
        ' 概要:
        '     現在の HTTP 要求の HttpSessionStateBase オブジェクトを取得します。
        '
        ' 戻り値:
        '     現在の HTTP 要求の HTTP セッション状態オブジェクト。
        Public ReadOnly Property Session As HttpSessionStateBase

このプロパティをテストコードから何とかして偽装してあげる必要がある。
そこで使用するのが、1でインストールしたMoqというパッケージ。

まずは、コントローラーを偽装する。

Imports Moq

<TestClass()> Public Class UnitTest1

    <TestMethod()> Public Sub TestMethod1()

        Dim controller As New Mock(Of ControllerContext)

    End Sub

End Class

ControllerContextなんて知らないよ、と波線が出た場合はMCVをインストールする。波線にカーソルを当てて、考えられる修正内容候補から選択するなり、テストプロジェクトの参照にMVCを追加するなりすればよい。

※追加するdllのバージョンは注意が必要。テストを行うWebアプリケーションのMVCのバージョンと合わせておくのが良いと思う。

以下が追加されているはずだが、ない場合は自分でimportsする。

Imports System.Web.Mvc

これで、コントローラーのモックが出来上がった。

ただし、中身がないのでこのままでは使用できない。このままだと、ただの箱だからだ。
今作りたいのは、セッションを偽装するためのコントローラー。なので、セッションを先に偽装してやる。

Imports System.Web.Mvc
Imports Moq

<TestClass()> Public Class UnitTest1

    <TestMethod()> Public Sub TestMethod1()

        Dim session As New Mock(Of HttpSessionStateBase)


        Dim controller As New Mock(Of ControllerContext)


    End Sub

End Class

HttpSessionStateBaseでまた波線がでるとおもう。これも、System.Web.dllの参照が足りないから。
Mvc同様の手順で追加してやる。
この手の奴は、大体「動かすまで」に苦労する。。

Imports System.Web
Imports System.Web.Mvc
Imports Moq

<TestClass()> Public Class UnitTest1

    <TestMethod()> Public Sub TestMethod1()

        Dim session As New Mock(Of HttpSessionStateBase)


        Dim controller As New Mock(Of ControllerContext)


    End Sub

End Class

これでやっと準備が整った。
では早速本題。

1.セッションのプロパティを設定する方法
例として、セッションIDを偽装してみる。

'
        ' 概要:
        '     Specifies a setup on the mocked type for a call to a non-void (value-returning)
        '     method.
        '
        ' パラメーター:
        '   expression:
        '     Lambda expression that specifies the method invocation.
        '
        ' 型パラメーター:
        '   TResult:
        '     Type of the return value. Typically omitted as it can be inferred from the expression.
        '
        ' 注釈:
        '     If more than one setup is specified for the same method or property, the latest
        '     one wins and is the one that will be executed.
        Public Function Setup(Of TResult)(expression As Expression(Of Func(Of T, TResult))) As ISetup(Of T, TResult)

Mockのメタデータをのぞいてみると、上記メソッドが使いやすそうなので、こちらを使用する。
※ほかのメソッドを使用したサンプルを紹介しているところもあるが、Stack overflowでの海外のエンジニアの反応を見る限りだと「時代遅れ」のメソッドのようだ。

        Dim session As New Mock(Of HttpSessionStateBase)
        session.Setup(Function(a) a.SessionID).Returns("MySessionId")

上記で、セッションIDが偽装された。戻り値として「MySessionId]を設定している。
これでセッションの偽装はできたので、次はこのセッションをコントローラーに乗せてやる。
それが下記コード。

        Dim controller As New Mock(Of ControllerContext)
        controller.Setup(Function(a) a.HttpContext.Session).Returns(session.Object)

コントローラーのコンテキスト.セッションプロパティを偽装して、戻り値として前で作成したセッションを設定した状態。最後にテスト確認を入れると下記となった。

    <TestMethod()> Public Sub TestMethod1()

        Dim session As New Mock(Of HttpSessionStateBase)
        session.Setup(Function(a) a.SessionID).Returns("MySessionId")

        Dim controller As New Mock(Of ControllerContext)
        controller.Setup(Function(a) a.HttpContext.Session).Returns(session.Object)

        Console.WriteLine(session.Object.SessionID)
        Assert.AreEqual(session.Object.SessionID, "MySessionId")

    End Sub

実行結果

テスト名:	TestMethod1
テスト成果:	成功
結果  の標準出力:	MySessionId

※Console.WriteLineを入れておくと、テスト時の標準出力が見れる。

2.セッションに新しいプロパティを作成して値を埋め込む方法
上記までの流れである程度想像はつくと思うので、コードのみの紹介とする。

    <TestMethod()> Public Sub TestMethod1()

        Dim session As New Mock(Of HttpSessionStateBase)
        session.Setup(Function(a) a("MyTestJsonSession")).Returns("{a:1,b:1,c:1}")

        Dim controller As New Mock(Of ControllerContext)
        controller.Setup(Function(a) a.HttpContext.Session).Returns(session.Object)

        Console.WriteLine(session.Object("MyTestJsonSession"))
        Assert.AreEqual(session.Object("MyTestJsonSession"), "{a:1,b:1,c:1}")

    End Sub

実行結果

テスト名:	TestMethod1
テスト成果:	成功
結果  の標準出力:	{a:1,b:1,c:1}

まとめ

テストの目的は、あくまでも成果物の基本的な正常動作を担保するものだ。なので、セッションを使用できることはあくまでも手段にすぎず、動作を担保するものにはならないので注意してほしい。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です