2016-07-24 50 views
25

我有一個ASP.NET MVC核心應用程序,我正在編寫單元測試。其中一種操作方法使用用戶名的某些功能:嘲笑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訪問。後者存儲在ControllerContext內。

最簡單的方法來替換用戶是通過分配一個不同的HttpContext與一個構造的用戶。我們可以使用DefaultHttpContext爲了這個目的,這樣你就不必嘲笑一切:

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

在我的情況下,它是'新的聲明(ClaimTypes.Name,「1」)'以匹配控制器使用'user.Identity.Name';但除此之外,這正是我想要達到的目標...... Danke schon! – Felix

+0

經過無數個小時的搜索,這是終於讓我擺脫的帖子。在我的核心2.0項目控制器方法中,我利用了'User.FindFirstValue(ClaimTypes.NameIdentifier);'在我創建的對象上設置了userId,並且因爲principal爲null而失敗。這對我來說是固定的。謝謝你的偉大答案! –

2

我想要實現抽象工廠模式。

爲工廠創建專門用於提供用戶名的界面。

然後提供具體的類,其中一個提供User.Identity.Name,另一個提供適用於您的測試的其他硬編碼值。

然後,您可以根據生產與測試代碼使用適當的具體類。也許希望通過工廠作爲參數,或者根據一些配置值切換到正確的工廠。

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

此外,用戶是ControllerBase的成員變量。這就是爲什麼在早期版本的ASP.NET人們嘲笑HttpContext,並從那裏獲得IPrincipal。一個人不能從一個獨立的班級獲得用戶,就像ProductionFactory – Felix

10

在以前的版本中,你可以有控制器,這對於一些非常簡單的單元測試取得直接設置User

如果你看他的源代碼ControllerBase你會注意到User是從HttpContext中提取的。

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

和控制器訪問HttpContext通過ControllerContext

/// <summary> 
/// Gets the <see cref="Http.HttpContext"/> for the executing action. 
/// </summary> 
public HttpContext HttpContext 
{ 
    get 
    { 
     return ControllerContext.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

也有使用現有類的可能性,並在需要時模擬而已。

var user = new Mock<ClaimsPrincipal>(); 
_controller.ControllerContext = new ControllerContext 
{ 
    HttpContext = new DefaultHttpContext 
    { 
     User = user.Object 
    } 
};