Moq:HttpContextに依存するメソッドの単体テスト
-
06-07-2019 - |
質問
.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()
の静的メソッドへの呼び出しに関連付けるにはどうすればよいですか? - このコード/アーキテクチャを改善する方法について提案はありますか
解決
Webformsはこの正確な理由でテストできないことで有名です-多くのコードはasp.netパイプラインの静的クラスに依存できます。
これをMoqでテストするには、 GetSecurityContextUserName()
メソッドをリファクタリングして、 HttpContextBase
オブジェクトで依存性注入を使用する必要があります。
HttpContextWrapper
は、.Net 3.5に同梱されている System.Web.Abstractions
にあります。これは HttpContext
クラスのラッパーであり、 HttpContextBase
を拡張し、次のように HttpContextWrapper
を構築できます。
var wrapper = new HttpContextWrapper(HttpContext.Current);
さらに良いのは、HttpContextBaseをモックし、Moqを使用して期待値を設定することです。ログインしたユーザーなどを含む
var mockContext = new Mock<HttpContextBase>();
これを適切に配置すると、 GetSecurityContextUserName(mockContext.Object)
を呼び出すことができ、アプリケーションは静的WebForms HttpContextとの結合がはるかに少なくなります。モック化されたコンテキストに依存する多くのテストを行う場合は、 takingを強くお勧めします。 Moqで使用するためのバージョンがあるScott HanselmanのMvcMockHelpersクラスを見てください。必要な多くのセットアップを便利に処理します。また、名前にもかかわらず、MVCで行う必要はありません。 HttpContextBase
を使用するようにリファクタリングできる場合、webformsアプリで正常に使用できます。
他のヒント
一般に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)がある)によく適合し、スレッドを正しく使用/チェックするコードで動作します。 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;
}
}
}
別の代替方法は、標準のセキュリティコンポーネントの場所を使用する代わりに、注入されたセキュリティの詳細を使用するアプリを作成することです。 GetCurrentUser()メソッドなどを使用してISecurityContextプロパティを追加し、それをアプリケーション全体で一貫して使用しますが、Webアプリケーションのコンテキストでこれを行う場合は、事前に構築された注入コンテキストを使用することもできます、HttpContextBase。
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を使用する必要はありません。
[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"));
(私たちのように)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;
}
カスタムプリンシパルを使用する場合、これらはインターフェイスにかなりうまく統合できます。たとえば、以下の Current
は GetCurrentPrincipal
および SetAsCurrent
を呼び出します。 SetCurrentPrincipal
を呼び出します。
public class MyCustomPrincipal : IPrincipal
{
public MyCustomPrincipal Current { get; }
public bool HasCurrent { get; }
public void SetAsCurrent();
}
これは、必要なものの単体テストにMoqを使用することとは実際には関係ありません。
通常、私たちの仕事は階層化されたアーキテクチャを持ち、プレゼンテーション層のコードは、UIに表示されるものを配置するためだけのものです。この種のコードは単体テストでカバーされていません。 UIはWinFormsアプリケーションであり、必ずしもWebアプリケーションではない可能性があるため、ロジックの残りのすべてはビジネスレイヤーに存在し、プレゼンテーションレイヤー(つまり、HttpContextなどのUI固有の参照)に依存する必要はありません。 。
このようにして、Mockフレームワークをいじったり、HttpRequestsなどをシミュレートしたりすることを避けることができます...