在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的測試方式,我們需要測試網站上的什麼動作都能自動化做到了