2009-07-31 72 views
35

考慮一個方法在.NET組件:起定量:單元測試依靠HttpContext的方法

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."); 

問題

  • 我如何使用最小起訂量來安排與像 'MYDOMAIN \ MYUSER' 一些價值假的HttpContext對象?
  • 我如何在MyIdentityBL.GetSecurityContextUserName()有我的電話,假冒聯想到我的靜態方法?
  • 你有關於如何提高這個代碼/架構有什麼建議?

回答

39

因爲這個確切的原因,Webforms是臭名昭着的無法測試 - 很多代碼可以依賴於asp.net管道中的靜態類。

爲了使用Moq進行測試,您需要重構GetSecurityContextUserName()方法以使用依賴注入與HttpContextBase對象。

HttpContextWrapper駐留在System.Web.Abstractions,其中附帶的.Net 3.5。它爲HttpContext類的包裝,並延伸HttpContextBase,你可以構造一個HttpContextWrapper就像這樣:

var wrapper = new HttpContextWrapper(HttpContext.Current); 

更妙的是,你可以模擬一個HttpContextBase和設置您的期望它使用起訂量上。包括登錄的用戶,等

var mockContext = new Mock<HttpContextBase>(); 

有了這個地方,你可以打電話GetSecurityContextUserName(mockContext.Object),和你的應用程序少得多耦合到靜態的WebForms的HttpContext。如果你打算做了很多的依靠嘲笑方面的測試,我強烈建議taking a look at Scott Hanselman's MvcMockHelpers class,其具有與起訂量使用的版本。它可以方便地處理很多必要的設置。儘管名字,你不需要用MVC那樣做 - 我成功地用它來與web表單的應用程序時,我可以重構他們使用HttpContextBase

+0

不能使用新HttpContextBase(),因爲它是一個抽象類(顧名思義)。您需要使用新的HttpContextWrapper()。 – 2009-11-19 17:53:23

+0

謝謝......當我回答這個問題時,我的腦袋超出了我的預期;) – womp 2009-11-19 20:12:09

0

如果你正在使用CLR安全模型(如我們一樣的),那麼你就需要使用一些抽象的函數來得到,如果你想允許測試設置當前主體,並利用這些獲取或設置每當主要。這樣做可以讓您在任何相關的地方獲取/設置主體(通常在網頁上的HttpContext以及其他單元測試中的當前線程上)。如果您使用自定義主那麼這些可以很好地公平地集成到它的接口,例如下面Current會打電話GetCurrentPrincipalSetAsCurrent會打電話SetCurrentPrincipal

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; 
} 

:這看起來是這樣。

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

這與使用Moq進行單元測試並非真正相關。

一般來說,我們在工作中都有一個分層架構,其中表示層上的代碼實際上只是用來安排在UI上顯示的東西。這種代碼不包含單元測試。所有其餘的邏輯駐留在業務層上,因爲UI可能也是一個WinForms應用程序,並且不一定是Web應用程序,所以它不必對錶示層具有任何依賴關係(即UI特定引用,如HttpContext) 。

通過這種方式,您可以避免混淆模擬框架,試圖模擬HttpRequests等......儘管通常它可能仍然是必需的。

3

一般來說,對於ASP.NET單元測試,而不是訪問HttpContext.Current,你應該有一個HttpContextBase類型的屬性,它的值是通過依賴注入設置的(例如在Womp提供的答案中)。

但是,對於測試安全相關的功能,我建議使用Thread.CurrentThread.Principal(而不是HttpContext.Current.User)。使用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; 
     } 
    } 
} 

另一種選擇是,而不是使用標準安全組件的位置,寫你的應用程序使用注入安全細節,例如使用GetCurrentUser()方法或類似方法添加ISecurityContext屬性,然後在整個應用程序中一致地使用它 - 但是如果要在Web應用程序的上下文中執行此操作,那麼您不妨使用預構建的注入上下文,HttpContextBase。

1

看一看這個 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
[TestInitialize] 
public void TestInit() 
{ 
    HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null)); 
} 

您也可以起訂量像下面

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"));