web-dev-qa-db-ja.com

Moq:HttpContextに依存するメソッドの単体テスト

.NETアセンブリのメソッドを考えます。

_public static string GetSecurityContextUserName()
{             
 //extract the username from request              
 string sUser = HttpContext.Current.User.Identity.Name;
 //everything after the domain     
 sUser = sUser.Substring(sUser.IndexOf("\\") + 1).ToLower();

 return sUser;      
}
_

Moqフレームワークを使用した単体テストからこのメソッドを呼び出したいのですが。このアセンブリは、webformsソリューションの一部です。単体テストは次のようになりますが、Moqコードがありません。

_//arrange 
 string ADAccount = "BUGSBUNNY";
 string fullADName = "LOONEYTUNES\BUGSBUNNY"; 

 //act    
 //need to mock up the HttpContext here somehow -- using Moq.
 string foundUserName = MyIdentityBL.GetSecurityContextUserName();

 //assert
 Assert.AreEqual(foundUserName, ADAccount, true, "Should have been the same User Identity.");
_

質問

  • Moqを使用して、「MyDomain\MyUser」のような値を持つ偽のHttpContextオブジェクトを配置するにはどうすればよいですか?
  • その偽物をMyIdentityBL.GetSecurityContextUserName()の静的メソッドへの呼び出しに関連付けるにはどうすればよいですか?
  • このコード/アーキテクチャを改善する方法について何か提案はありますか?
38
p.campbell

Webformsは、この正確な理由でテストできないことで有名です。多くのコードは、asp.netパイプラインの静的クラスに依存する可能性があります。

これをMoqでテストするには、HttpContextBaseオブジェクトで依存関係注入を使用するようにGetSecurityContextUserName()メソッドをリファクタリングする必要があります。

HttpContextWrapperは、.Net 3.5に同梱されている_System.Web.Abstractions_にあります。これはHttpContextクラスのラッパーであり、HttpContextBaseを拡張し、次のようにHttpContextWrapperを構築できます。

_var wrapper = new HttpContextWrapper(HttpContext.Current);
_

さらに良いことに、HttpContextBaseをモックし、Moqを使用してHttpContextBaseに期待を設定できます。ログインしているユーザー等を含む.

_var mockContext = new Mock<HttpContextBase>();
_

これを実行すると、GetSecurityContextUserName(mockContext.Object)を呼び出すことができ、アプリケーションは静的WebForms HttpContextとの結合がはるかに少なくなります。モックされたコンテキストに依存する多くのテストを行うつもりなら、私は強くお勧めします Scott HanselmanのMvcMockHelpersクラスを見てください 。Moqで使用するためのバージョンがあります。必要な設定の多くを便利に処理します。そして、名前にもかかわらず、MVCでそれを行う必要はありません。HttpContextBaseを使用するようにリファクタリングできる場合は、webformsアプリで正常に使用します。

43
womp

一般に、ASP.NETの単体テストでは、HttpContext.Currentにアクセスするのではなく、依存関係注入(Wompによって提供される回答など)によって値が設定されるHttpContextBase型のプロパティが必要です。

ただし、セキュリティ関連の機能をテストする場合は、HttpContext.Current.Userの代わりにThread.CurrentThread.Principalを使用することをお勧めします。 Thread.CurrentThreadを使用すると、Webコンテキスト外でも再利用できるという利点があります(ASP.NETフレームワークは常に両方の値を同じに設定するため、Webコンテキストでも同じように機能します)。

次に、Thread.CurrentThread.Principalをテストするために、通常はThread.CurrentThreadをテスト値に設定し、次に破棄時にリセットするスコープクラスを使用します。

using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
    // Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
}

これは、標準の.NETセキュリティコンポーネント(コンポーネントに既知のインターフェイス(IPrincipal)と場所(Thread.CurrentThread.Principal)がある場合)にうまく適合し、Thread.CurrentThread.Principalを正しく使用/チェックするすべてのコードで機能します。 。

基本スコープクラスは次のようになります(役割の追加などの必要に応じて調整してください)。

class UserResetScope : IDisposable {
    private IPrincipal originalUser;
    public UserResetScope(string newUserName) {
        originalUser = Thread.CurrentPrincipal;
        var newUser = new GenericPrincipal(new GenericIdentity(newUserName), new string[0]);
        Thread.CurrentPrincipal = newUser;
    }
    public IPrincipal OriginalUser { get { return this.originalUser; } }
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            Thread.CurrentPrincipal = originalUser;
        }
    }
}

もう1つの方法は、標準のセキュリティコンポーネントの場所を使用する代わりに、注入されたセキュリティの詳細を使用するようにアプリを作成することです。 GetCurrentUser()メソッドなどでISecurityContextプロパティを追加し、アプリケーション全体で一貫して使用します。ただし、これをWebアプリケーションのコンテキストで行う場合は、事前に作成された注入コンテキストを使用することもできます。 、HttpContextBase。

3
Sly Gryphon

これを見てください http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

HttpSimulatorクラスを使用すると、HttpContextをハンドラーに渡すことができます

HttpSimulator sim = new HttpSimulator("/", @"C:\intepub\?")
.SimulateRequest(new Uri("http://localhost:54331/FileHandler.ashx?
ticket=" + myticket + "&fileName=" + path));

FileHandler fh = new FileHandler();
fh.ProcessRequest(HttpContext.Current);

HttpSimulatorは、HttpContextインスタンスを取得するために必要なものを実装します。したがって、ここではMoqを使用する必要はありません。

1
Davut Gürbüz
[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}

また、あなたは以下のようにmoqすることができます

var controllerContext = new Mock<ControllerContext>();
      controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
      controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));
1
PUG

(私たちのように)CLRセキュリティモデルを使用している場合、テストを許可する場合は、いくつかの抽象化された関数を使用して現在のプリンシパルを取得および設定し、プリンシパルを取得または設定するときは常にこれらを使用する必要があります。これを行うと、関連する場所(通常、WebのHttpContext上、および単体テストのような他の場所の現在のスレッド上)でプリンシパルを取得/設定できます。これは次のようになります。

public static IPrincipal GetCurrentPrincipal()
{
    return HttpContext.Current != null ?
        HttpContext.Current.User :
        Thread.CurrentThread.Principal;
}

public static void SetCurrentPrincipal(IPrincipal principal)
{
     if (HttpContext.Current != null) HttpContext.Current.User = principal'
     Thread.CurrentThread.Principal = principal;
}

カスタムプリンシパルを使用する場合、これらはそのインターフェースにかなりうまく統合できます。たとえば、以下では、CurrentGetCurrentPrincipalを呼び出し、SetAsCurrentSetCurrentPrincipalを呼び出します。

public class MyCustomPrincipal : IPrincipal
{
    public MyCustomPrincipal Current { get; }
    public bool HasCurrent { get; }
    public void SetAsCurrent();
}
0
Greg Beech

これは、必要なものの単体テストにMoqを使用することとは実際には関係ありません。

一般に、私たちの職場には階層化されたアーキテクチャがあり、プレゼンテーション層のコードは、UIに表示されるものを配置するためのものです。この種のコードは単体テストの対象外です。残りのロジックはすべてビジネスレイヤーにあります。UIもWinFormsアプリケーションであり、必ずしもWebアプリケーションではない場合があるため、プレゼンテーションレイヤー(つまり、HttpContextなどのUI固有の参照)に依存する必要はありません。 。

このようにして、モックフレームワークをいじったり、HttpRequestsをシミュレートしたりすることなどを避けることができます。

0
Juri