[翻譯] ASP.NET MVC Tip #12 – 仿製控制器上下文

來源:互聯網
上載者:User
ASP.NET MVC Tip #12 – 仿製控制器上下文

原文地址:http://weblogs.asp.net/stephenwalther/archive/2008/06/30/asp-net-mvc-tip-12-faking-the-controller-context.aspx

翻譯:Anders Liu

摘要:在這個Tip中,Stephen Walther介紹了在為ASP.NET MVC應用程式建立單元測試時,如何深入ASP.NET內部進行測試。Stephen Walther介紹了如何建立一組標準的仿製對象(Fake Object)來模仿目前使用者、目前使用者角色、請求參數、工作階段狀態和Cookie。

ASP.NET MVC用程式比ASP.NET Web Forms應用程式更加可測試。ASP.NET MVC的每個特性從設計伊始就一直注意可測試性。然而,ASP.NET MVC應用那個程式中還是有一些方面是難以測試的。尤其你會發現,在ASP.NET MVC中測試ASP.NET內部仍然是一個挑戰。

我所說的“ASP.NET內部”是什麼意思呢?就是指那些出現在HttpContext中的東西。也就是這些對象:

  • Request.Forms——POST到一個頁面的表單參數。
  • Request.QueryString——傳遞到一個頁面的查詢字串參數。
  • User——發起頁面請求的目前使用者。
  • Reqest.Cookies——傳遞到頁面的瀏覽器Cookie。
  • Session——工作階段狀態對象。

例如,假設你想對一個特定的控制器——其實是一個特定的工作階段狀態條目——進行測試,你需要建立類似下面這樣的單元測試:

<br />[TestMethod]<br />public void TestSessionState()<br />{<br /> // Arrange<br /> var controller = new HomeController();<br /> // Act<br /> var result = controller.TestSession() as ViewResult;<br /> // Assert<br /> Assert.AreEqual("wow!", controller.HttpContext.Session["item1"]);<br />}<br />

該測試檢查名為TestSession()的控制器操作是否向工作階段狀態中添加了一個新的名叫item1的條目、其值是否為"wow!"。

下面的控制器操作可以通過這一測試:

<br />public ViewResult TestSession()<br />{<br /> Session["item1"] = "wow!";<br /> return View();<br />}<br />

該控制操作向工作階段狀態中插入了一個具有期望值的條目。

不幸的是,如果你運行該單元測試,測試會失敗。失敗的原因是出現了一個NullReferenceException異常。此處的問題在於在單元測試的上下文中,工作階段狀態並不存在。事實上,在一個測試方法中,任何ASP.NET內部的東西都不存在。這意味著你無法測試Cookies、表單參數、查詢字串參數和使用者實體或使用者角色。

Mocking VS Stubbing

如果你需要編寫一個使用者到了ASP.NET內部對象的單元測試,那麼你必須做出選擇。你有兩種選擇,一是使用Mock Ojbect Framework,或者是使用一組仿製類(Fake Class)。

第一個選擇是偽造(mock)ASP.NET內部對象,這可以使用Moq、Typemock Isolator或Rhino Mocks這樣的Mock Ojbect Framework來完成。使用這些架構中的任何一種都可以產生假扮ASP.NET內部對象的對象。如果你想更多地瞭解這些架構,可以看我之前針對這三種Mock Ojbect Framework撰寫的部落格:

  • http://weblogs.asp.net/stephenwalther/archive/2008/06/11/tdd-introduction-to-moq.aspx
  • http://weblogs.asp.net/stephenwalther/archive/2008/03/22/tdd-introduction-to-rhino-mocks.aspx
  • http://weblogs.asp.net/stephenwalther/archive/2008/03/16/tdd-introduction-to-typemock-isolator.aspx

另外一種選擇就是建立一組類,用於類比ASP.NET內部對象。這組類可以只建立一次,然後在將來所有的ASP.NET MVC項目中使用它們。

在這個Tip中,我將介紹第二種方法。我將向你展示如何通過建立一組標準的ASP.NET內部對象仿製類來簡單地測試ASP.NET內部對象,而無需使用Mock Object Framework。

建立仿製控制器上下文

在該Tip的結尾,你可以下載到這些仿製類。我建立了一組ASP.NET內部對象仿製類,名字分別是:

  • FakeControllerContext
  • FakeHttpContext
  • FakeHttpRequest
  • FakeHttpSessionState
  • FakeIdentity
  • FakePrincipal

在單元測試中建立FakeControllerContext的執行個體,並將其賦值給控制器的ControllerContext屬性,就可以開始使用這些仿製類了。例如,下面展示了如何利用FakeControllerContext類來在單元測試中仿製一個特定的使用者。

<br />var controller = new HomeController();<br />controller.ControllerContext = new FakeControllerContext(controller, "Stephen");<br />

當把FakeControllerContext賦給控制器後,在單元測試的其餘部分,控制器將會使用這個上下文。讓我們來通過不同的例子看一看如何使用FakeControllerContext來類比不同的ASP.NET內部對象。

測試表單參數

假設你希望向操作傳遞不同的表單參數來測試控制器操作的行為。另外,假設控制器操作象下面這樣直接存取Reques.Form:

<br />public ActionResult Insert()<br />{<br /> ViewData["firstname"] = Request.Form["firstName"];<br /> ViewData["lastName"] = Request.Form["lastName"];<br /> return View();<br />}<br />

如何測試控制器操作呢?對於這種情況,你可以使用接受一組表單參數的FakeControllerContext構造器。下面的測試檢查了firstName和lastName表單參數是否被儲存到了視圖資料中:

<br />[TestMethod]<br />public void TestFakeFormParams()<br />{<br /> // Create controller<br /> var controller = new HomeController();<br /> // Create fake controller context<br /> var formParams = new NameValueCollection { { "firstName", "Stephen" }, {"lastName", "Walther"} };<br /> controller.ControllerContext = new FakeControllerContext(controller, formParams);<br /> // Act<br /> var result = controller.Insert() as ViewResult;<br /> Assert.AreEqual("Stephen", result.ViewData["firstName"]);<br /> Assert.AreEqual("Walther", result.ViewData["lastName"]);<br />}<br />

FakeControllerContext的表單參數是通過一個NameValueCollection建立的。表單參數的仿製集合被傳遞給FakeControllerContext的構造器。

測試查詢字串參數

假設你需要測試查詢字串參數是否被傳遞到一個視圖中。查詢字串可以直接通過Request.QueryString集合訪問。例如,控制器操作看起來可能是下面這樣:

<br />public ViewResult Details()<br />{<br /> ViewData["key1"] = Request.QueryString["key1"];<br /> ViewData["key2"] = Request.QueryString["key2"];<br /> ViewData["count"] = Request.QueryString.Count;<br /> return View();<br />}<br />

在這種情況下,你可以通過將一個NameValueCollection傳遞給FakeControllerContext的構造器來仿製查詢字串:

<br />[TestMethod]<br />public void TestFakeQueryStringParams()<br />{<br /> // Create controller<br /> var controller = new HomeController();<br /> // Create fake controller context<br /> var queryStringParams = new NameValueCollection { { "key1", "a" }, { "key2", "b" } };<br /> controller.ControllerContext = new FakeControllerContext(controller, null, queryStringParams);<br /> // Act<br /> var result = controller.Details() as ViewResult;<br /> Assert.AreEqual("a", result.ViewData["key1"]);<br /> Assert.AreEqual("b", result.ViewData["key2"]);<br /> Assert.AreEqual(queryStringParams.Count, result.ViewData["count"]);<br />}<br />

注意查詢字串要作為FakeControllerContext構造器的第二個參數傳入。

測試使用者

你可能需要測試控制器操作的安全性。例如,你可能希望僅為一個特定的已驗證使用者顯示某個視圖。下面的控制器操作為已驗證使用者顯示一個Secret視圖,而將所有其他使用者重新導向到Index視圖:

<br />public ActionResult Secret()<br />{<br /> if (User.Identity.IsAuthenticated)<br /> {<br /> ViewData["userName"] = User.Identity.Name;<br /> return View("Secret");<br /> }<br /> else<br /> {<br /> return RedirectToAction("Index");<br /> }<br />}<br />

你可以使用FakeController來測試該操作的行為是否為你預期的:

<br />[TestMethod]<br />public void TestFakeUser()<br />{<br /> // Create controller<br /> var controller = new HomeController();</p><p> // Check what happens for authenticated user<br /> controller.ControllerContext = new FakeControllerContext(controller, "Stephen");<br /> var result = controller.Secret() as ActionResult;<br /> Assert.IsInstanceOfType(result, typeof(ViewResult));<br /> ViewDataDictionary viewData = ((ViewResult) result).ViewData;<br /> Assert.AreEqual("Stephen", viewData["userName"]);</p><p> // Check what happens for anonymous user<br /> controller.ControllerContext = new FakeControllerContext(controller);<br /> result = controller.Secret() as ActionResult;<br /> Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));<br />}<br />

該測試實際上測試了三樣東西(也許應該分成多個測試)。首先,它測試了一個以驗證使用者是否從控制器操作取得了所需的視圖。其次,它測試了已驗證使用者的使用者名稱是否被成功地添加到了試圖資料中。最後,它檢查了匿名使用者在控制其操作執行時是否被重新導向了。

測試使用者角色

你可能希望根據角色的不同,為不同的使用者顯示不同的內容。例如,某些內容可能只能由管理員(Admins)瀏覽。假設你有一個類似下面這樣的控制器操作:

<br />public ActionResult Admin()<br />{<br /> if (User.IsInRole("Admin"))<br /> {<br /> return View("Secret");<br /> }<br /> else<br /> {<br /> return RedirectToAction("Index");<br /> }<br />}<br />

如果你是一個具備Admin角色的成員,該控制器操作將返回Secret視圖。

你可以通過下面的測試方法測試該控制器操作:

<br />[TestMethod]<br />public void TestFakeUserRoles()<br />{<br /> // Create controller<br /> var controller = new HomeController();</p><p> // Check what happens for Admin user<br /> controller.ControllerContext = new FakeControllerContext(controller, "Stephen", new string[] {"Admin"});<br /> var result = controller.Admin() as ActionResult;<br /> Assert.IsInstanceOfType(result, typeof(ViewResult));</p><p> // Check what happens for anonymous user<br /> controller.ControllerContext = new FakeControllerContext(controller);<br /> result = controller.Admin() as ActionResult;<br /> Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));<br />}<br />

該操作能夠驗證是否只有Admin角色的成員能夠看到Secret視圖。該測試還檢查了匿名使用者是否被重新導向到了其他頁面。

測試瀏覽器Cookies

假設你需要測試訪問了瀏覽器Cookies的操作。例如,你可能通過瀏覽器端Cookies傳遞了一個客戶ID。如何測試這類操作呢?

下面的控制器方法簡單地講一個瀏覽器Cookie添加到了視圖資料中:

<br />public ViewResult TestCookie()<br />{<br /> ViewData["key"] = Request.Cookies["key"].Value;<br /> return View();<br />}<br />

你可以通過建立一個SessionItemCollection並將其傳遞到FakeControllerContext中來測試該控制器操作:

<br />[TestMethod]<br />public void TestCookies()<br />{<br /> // Create controller<br /> var controller = new HomeController();</p><p> // Create fake Controller Context<br /> var cookies = new HttpCookieCollection();<br /> cookies.Add( new HttpCookie("key", "a"));<br /> controller.ControllerContext = new FakeControllerContext(controller, cookies);<br /> var result = controller.TestCookie() as ViewResult;</p><p> // Assert<br /> Assert.AreEqual("a", result.ViewData["key"]);<br />}<br />

該測實驗證了從知其操作是否將名為key的Cookie添加到了視圖資料中。

測試工作階段狀態

最後的一個例子了。我們來看一下測試訪問了工作階段狀態的控制器操作:

<br />public ViewResult TestSession()<br />{<br /> ViewData["item1"] = Session["item1"];<br /> Session["item2"] = "cool!";<br /> return View();<br />}<br />

該控制器操作同時讀和寫了工作階段狀態。它從工作階段狀態中取出了一個名為item1的條目,並將其添加到試圖資料中。該控制器操作還建立了一個名為item2的工作階段狀態條目。

使用下面的單元測試可以測試該控制器操作:

<br />[TestMethod]<br />public void TestSessionState()<br />{<br /> // Create controller<br /> var controller = new HomeController();</p><p> // Create fake Controller Context<br /> var sessionItems = new SessionStateItemCollection();<br /> sessionItems["item1"] = "wow!";<br /> controller.ControllerContext = new FakeControllerContext(controller, sessionItems);<br /> var result = controller.TestSession() as ViewResult;</p><p> // Assert<br /> Assert.AreEqual("wow!", result.ViewData["item1"]);</p><p> // Assert<br /> Assert.AreEqual("cool!", controller.HttpContext.Session["item2"]);<br />}<br />

注意這裡建立了一個SessionStateItemCollection並將其傳給了FakeControllerContext的構造器。SessionStateItemCollection表示工作階段狀態中的所有條目。

小結

在這個Tip中,我介紹了如何測試ASP.NET內部對象——如表單參數、查詢字串、使用者實體、使用者角色、Cookies和工作階段狀態——使用的是一組標準的仿製類。通過下面的連結可以下載到這些仿製類的完整代碼(同時有C#和VB.NET代碼)。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.