FormsAuthenticationTicket UserData和UnitTesting

时间:2012-07-13 12:18:38

标签: c# unit-testing forms-authentication asp.net-mvc-4

我正在尝试在MVC中进行一些单元测试,但是我的一些功能需要UserID才能工作(即作为外键保存到数据库)。

UserID存储在我UserData的{​​{1}}属性中,如何在单位测试时伪造一个UserID,以便我可以使用假用户进行单元测试。这甚至可能吗?

我正在使用微软内置的单元测试系统。

我计划使用的测试代码类似于

FormsAuthenticationTicket

一些示例代码可能如下所示: -

[TestMethod]
public void EnsureAddItemAddsItems()
{
   // Arrange
   ShoppingCartController controller = new ShoppingCartController();
   // These would be populated by dummy values
   Guid itemID = Guid.Empty;
   Guid itemTypeID = Guid.Empty;

   // Act
   ViewResult result = controller.AddItem(itemTypeID, itemID);

   //Assert
   Assert.AreEqual("Your shopping cart contains 1 item(s)",result.ViewBag.Message);
}

highlited行是我获取userID的地方,该函数只是一个不做任何复杂的扩展函数,它只是获取保存为public ActionResult AddItem(Guid itemType, Guid itemID, string returnAction) { ShoppingCartViewModel oModel = new ShoppingCartViewModel(); string szName = String.Empty; int price = 0; using (var context = new entityModel()) { // This is the section I'm worries about >>>>>>>>> Guid gUserID = this.GetCurrentUserID(); <<<<<<<<< var currentSession = this.CreateOrContinueCurrentCartSession(gUserID); var oItem = new ShoppingCartItem(); oItem.Id = Guid.NewGuid(); oItem.ItemId = itemID; oItem.ItemTypeId = itemType; oItem.ItemName = szName; oItem.ShoppingCartSessionId = currentSession.ID; oItem.Price = 1; context.ShoppingCartItems.AddObject(oItem); context.SaveChanges(); } this.FlashInfo("Item added to shopping cart"); ViewBag.Message(string.Format("Your shopping cart contains {0} item(s)",AnotherFunctionThatJustCountsTheValues())); return this.View(); } 字段的UserID。

1 个答案:

答案 0 :(得分:4)

  

//这是我担心Guid gUserID = this.GetCurrentUserID();

的部分

这是因为通常这个部分在你的控制器动作中无关。所以,让我们删除它,好吗?这样你就不用担心了: - )

让我们首先简化您的代码,因为它包含太多噪音。顺便说一句,你应该考虑putting your controller action on a diet,因为它太复杂了,做了太多事情。

关键部分是:

[Authorize]
public ActionResult AddItem()
{
    Guid gUserID = this.GetCurrentUserID();
    return Content(gUserID.ToString());
}

现在,这确实很烦人,因为如果GetCurrentUserID方法驻留在您的控制器中并尝试读取表单身份验证cookie,您可能会单独对其进行单元测试。

如果我们的代码看起来像这样:

[MyAuthorize]
public ActionResult AddItem()
{
    var user = (MyUser)User;
    return Content(user.Id.ToString());
}

其中MyUser是自定义主体:

public class MyUser : GenericPrincipal
{
    public MyUser(IIdentity identity, string[] roles) : base(identity, roles)
    { }

    public Guid Id { get; set; }
}

这不是很壮观吗?通过这种方式,控制器操作不再需要担心cookie和票证和内容。这不是它的责任。

让我们看看我们现在如何进行单元测试。我们选择我们最喜欢的模拟框架(Rhino MocksMvcContrib.TestHelper)并模拟:

[TestMethod]
public void AddItem_Returns_A_Content_Result_With_The_Current_User_Id()
{
    // arrange
    var sut = new HomeController();
    var cb = new TestControllerBuilder();
    cb.InitializeController(sut);
    var user = new MyUser(new GenericIdentity("john"), null)
    {
        Id = Guid.NewGuid(),
    };
    cb.HttpContext.User = user;

    // act
    var actual = sut.AddItem();

    // assert
    actual
        .AssertResultIs<ContentResult>()
        .Content
        .Equals(user.Id.ToString());
}

所以现在剩下的就是看看自定义[MyAuthorize]属性的外观:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var authorized = base.AuthorizeCore(httpContext);
        if (!authorized)
        {
            return false;
        }

        var cookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
        if (cookie == null)
        {
            return false;
        }

        var ticket = FormsAuthentication.Decrypt(cookie.Value);
        var id = Guid.Parse(ticket.UserData);
        var identity = new GenericIdentity(ticket.Name);
        httpContext.User = new MyUser(identity, null)
        {
            Id = id
        };
        return true;
    }
}

自定义authorize属性负责读取表单身份验证cookie的UserData部分并将当前主体设置为我们的自定义主体。