2016-07-24 12 views
25

私はユニットテストを書くASP.NET MVCコアアプリケーションを持っています。アクションメソッドの1つは、一部の機能にユーザー名を使用します。ASP.NETコアでIPrincipalをモックする

SettingsViewModel svm = _context.MySettings(User.Identity.Name); 

これは明らかにユニットテストで失敗します。私は周りを見回し、すべての提案は.NET 4.5からHttpContextを疑似するためのものです。私はそれを行うためのより良い方法があると確信しています。 IPrincipalを注入しようとしましたが、エラーが発生しました。私はこれを試してみました。(必死ではないと思います。)

public IActionResult Index(IPrincipal principal = null) { 
    IPrincipal user = principal ?? User; 
    SettingsViewModel svm = _context.MySettings(user.Identity.Name); 
    return View(svm); 
} 

しかしこれもエラーを投げました。 ドキュメント内に何も見つかりませんでした。

答えて

46

ようになり、コントローラのUserは、コントローラのHttpContextを介してアクセスされます。 LatterはControllerContextに保存されています。

ユーザーを置き換える最も簡単な方法は、構築されたユーザーに別のHttpContextを割り当てることです。

var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] 
{ 
    new Claim(ClaimTypes.NameIdentifier, "1"), 
    new Claim(MyCustomClaim, "example claim value") 
})); 

var controller = new SomeController(dependencies…); 
controller.ControllerContext = new ControllerContext() 
{ 
    HttpContext = new DefaultHttpContext() { User = user } 
}; 
+5

私の場合、 'user.Identity.Name'のコントローラの使用に合うのは' claim(ClaimTypes.Name、 "1") 'です。それ以外は、私が達成しようとしていたものです... Danke schon! – Felix

+0

数え切れないほどの時間を捜した後、これが最終的に私を去らせたポストでした。私のコア2.0プロジェクトコントローラのメソッドでは、私が作成していたオブジェクトにuserIdを設定するために 'User.FindFirstValue(ClaimTypes.NameIdentifier);'を利用していましたが、プリンシパルがnullだったので失敗していました。これは私のために修正されました。素晴らしい答えをありがとう! –

2

抽象ファクトリパターンを実装すると思います。

ユーザ名を提供するための特別なファクトリ用のインタフェースを作成します。

具体的なクラスを提供してください。具体的なクラスはUser.Identity.Nameで、テストでは他のハードコード値を提供します。

生産対テストコードに応じて、適切な具象クラスを使用できます。おそらく、パラメータとしてファクトリを渡すか、またはいくつかの設定値に基づいて正しいファクトリに切り替えることを検討します。あなたには、いくつかの非常に簡単にユニットテストのために作られたコントローラー、上で直接User設定している可能性があり、以前のバージョンでは

interface IUserNameFactory 
{ 
    string BuildUserName(); 
} 

class ProductionFactory : IUserNameFactory 
{ 
    public BuildUserName() { return User.Identity.Name; } 
} 

class MockFactory : IUserNameFactory 
{ 
    public BuildUserName() { return "James"; } 
} 

IUserNameFactory factory; 

if(inProductionMode) 
{ 
    factory = new ProductionFactory(); 
} 
else 
{ 
    factory = new MockFactory(); 
} 

SettingsViewModel svm = _context.MySettings(factory.BuildUserName()); 
+0

ありがとうございます。私は* my *オブジェクトのために同様のことをやっています。私はちょうどIPrinicpalのような共通のもののために、 "箱から"何かがあることを望んでいた。しかし、明らかにそうではありません! – Felix

+0

さらに、UserはControllerBaseのメンバー変数です。そのため、以前のバージョンのASP.NETユーザーはHttpContextを嘲笑し、そこからIPrincipalを取得していました。プロダクションファクトリー – Felix

10

ソースコードControllerBaseを見ると、UserHttpContextから抽出されています。

/// <summary> 
/// Gets or sets the <see cref="ClaimsPrincipal"/> for user associated with the executing action. 
/// </summary> 
public ClaimsPrincipal User 
{ 
    get 
    { 
     return HttpContext?.User; 
    } 
} 

、コントローラはあなたがこれらの二つのプロパティのみを読み込むされていることがわかりますControllerContext

/// <summary> 
/// Gets the <see cref="Http.HttpContext"/> for the executing action. 
/// </summary> 
public HttpContext HttpContext 
{ 
    get 
    { 
     return ControllerContext.HttpContext; 
    } 
} 

経由HttpContextにアクセスします。良いことは、ControllerContextプロパティで値を設定できるようにすることです。

ターゲットはそのオブジェクトを取得することです。コアではHttpContextは抽象的なので、模擬する方がはるかに簡単です。

部品番号を使用し

public class MyController : Controller { 
    IMyContext _context; 

    public MyController(IMyContext context) { 
     _context = context; 
    } 

    public IActionResult Index() { 
     SettingsViewModel svm = _context.MySettings(User.Identity.Name); 
     return View(svm); 
    } 

    //...other code removed for brevity 
} 

ようなコントローラを仮定すると、テストはこの

public void Given_User_Index_Should_Return_ViewResult_With_Model() { 
    //Arrange 
    var username = "FakeUserName"; 
    var identity = new GenericIdentity(username, ""); 

    var mockPrincipal = new Mock<IPrincipal>(); 
    mockPrincipal.Setup(x => x.Identity).Returns(identity); 
    mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true); 

    var mockHttpContext = new Mock<HttpContext>(); 
    mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object); 

    var model = new SettingsViewModel() { 
     //...other code removed for brevity 
    }; 

    var mockContext = new Mock<IMyContext>(); 
    mockContext.Setup(m => m.MySettings(username)).Returns(model); 

    var controller = new MyController(mockContext.Object) { 
     ControllerContext = new ControllerContext { 
      HttpContext = mockHttpContext.Object 
     } 
    }; 

    //Act 
    var viewResult = controller.Index() as ViewResult; 

    //Assert 
    Assert.IsNotNull(viewResult); 
    Assert.IsNotNull(viewResult.Model); 
    Assert.AreEqual(model, viewResult.Model); 
} 
+0

のようなスタンドアロンクラスからユーザーを取得することはできません。(ありがとう)。実際には、私はあなたに支払うことがあります:)私はより複雑な文脈でこの解決策を試すことがあります。私は両方の回答を受け入れることができたらいいなあ! – Felix

0

可能性既存のクラスを使用して、必要なときだけ模擬するためにもあります:私たちは、あなたがすべてを模擬する必要はありませんその方法は、この目的のためにDefaultHttpContextを使用することができます。

var user = new Mock<ClaimsPrincipal>(); 
_controller.ControllerContext = new ControllerContext 
{ 
    HttpContext = new DefaultHttpContext 
    { 
     User = user.Object 
    } 
}; 
関連する問題