使用UnitTest來測試有Session的Action

  • 881
  • 0
  • C#
  • 2019-05-12

在UnitTest的專案裡,我們除了可以拿來測Function或是EntityFramework的執行情況,那我們是不是也可以拿來模擬網頁上HttpPost資料給controller中的某個Action的動作呢?

 要測試Controller裡的Action通常我們會分成HttpGet或是HttpPost,但在UnitTest似乎沒關係

所以我把重點切成,你要測的Action是否會用到Session的資料

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來測

三兩下我們就測完了

Action裡需要用到Session資料

在測試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這個東西

使用Moq

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