在UnitTest的專案裡,我們除了可以拿來測Function或是EntityFramework的執行情況,那我們是不是也可以拿來模擬網頁上HttpPost資料給controller中的某個Action的動作呢?
要測試Controller裡的Action通常我們會分成HttpGet或是HttpPost,但在UnitTest似乎沒關係
所以我把重點切成,你要測的Action是否會用到Session的資料
假設我們的controller裡有一個action是HelloWolrd
[HttpGet]
public ActionResult HelloWolrd(string name)
{
return Json("Hello, " + name);
}
雖然這個action非常簡單,連測都不用測,但如果今天要用Unit Test來測怎麼辦
[TestMethod]
public void TestHelloWorld()
{
var controller = new HomeController();
var result = controller.HelloWolrd("Ace");
Console.WriteLine(((System.Web.Mvc.JsonResult)(result)).Data);
}
我們可以new一個HomeController就可以了,就把HomeController當成一個class來測
三兩下我們就測完了
在測試HttpPost的資料時,我們通常會在網頁上要求user登入,並把user的資訊存入Session方便使用
這時候我們就會在UnitTest中遇上麻煩了
假設我們今天登入了,並有一個create user的動作
我們要在unit test中測試這個create user的動作是否會產生什麼問題
UserViewModel是要放我們登入時所存的資訊
OutputUserViewModel則是呼叫CreateUser後所回傳的物件
public class UserViewModel
{
public string email { set; get; }
public int ID { set; get; }
public int TimeZoneOffSet { set; get; }
}
public class OutputUserViewModel
{
public string newCreatedUser { set; get; }
public int CreatedByID { set; get; }
public string CreatedByEmail { set; get; }
public DateTime CreateDateTime{ set; get; }
}
而CreateUser的Action為求簡便說明,則是把登入的資訊跟要create的資訊Mixed成另一個新的物件回傳
CurrentUser則是抓Session的資料
[HttpPost]
public ActionResult CreateUser(string UserName)
{
return Json(new OutputUserViewModel()
{
newCreatedUser = UserName,
CreatedByID = CurrentUser.ID,
CreatedByEmail = CurrentUser.email,
CreateDateTime = DateTime.UtcNow
});
}
private UserViewModel CurrentUser
{
get
{
if (Session["myCurrentUser"] == null)
{
return null;
}
return (UserViewModel)Session["myCurrentUser"];
}
set
{
Session["myCurrentUser"] = value;
}
}
以之前的例子,我們依樣畫葫蘆
[TestMethod]
public void TestControllerAction()
{
var controller = new HomeController();
var result = controller.CreateUser("someBody");
OutputUserViewModel output = (OutputUserViewModel)((System.Web.Mvc.JsonResult)(result)).Data;
Assert.AreEqual(1, output.CreatedByID);
}
在執行UnitTest則會出錯,原因無他,因為我們是在UnitTest的環境,而不是在Web的環境
並沒有Session這個東西
可以在Nuget上面使用Moq (mock)這個元件幫你模擬Session的資料,以方便測試
在取得Session的部份,我們用了一個fakeLogin的Function以偽裝我們登入了網站
並回傳一個登入的資訊並存到Session去
動作大概是這樣,它會幫你模擬出一個物件,當你要使用的時候,才把真正的Session丟進去
記得這個Session的名稱myCurrentUser要跟你的Action裡的Session name要一致
private Mock<ControllerContext> GetMockSessionController(string email)
{
UserViewModel mySession = fakeLogin(email);
var mockControllerContext = new Mock<ControllerContext>();
var mockSession = new Mock<HttpSessionStateBase>();
mockSession.SetupGet(s => s["myCurrentUser"]).Returns(mySession);
mockControllerContext.Setup(p => p.HttpContext.Session).Returns(mockSession.Object);
return mockControllerContext;
}
public UserViewModel fakeLogin(string email)
{
//You may need to check your db with email
return new UserViewModel() { email = email, ID = 1, TimeZoneOffSet = -8 };
}
而我們的Unit Test則會變成這樣
把HomeController的context用我們剛剛模擬出來的物件丟進去,如此一來便可模擬有session的測試環境
[TestMethod]
public void TestControllerActionWithMock()
{
var mockControllerContext = GetMockSessionController("Ace.Lee@xxx");
var controller = new HomeController();
controller.ControllerContext = mockControllerContext.Object;
var result = controller.CreateUser("someBody");
OutputUserViewModel output = (OutputUserViewModel)((System.Web.Mvc.JsonResult)(result)).Data;
Assert.AreEqual(1, output.CreatedByID);
}
萬歲,果然真的抓到我們模擬出來的Session了
有了Mock的測試方式,我們需要測試網站上的什麼動作都能自動化做到了