如何在ShoppingCart控制器/模型中模拟HttpContext

时间:2013-12-16 10:24:21

标签: c# entity-framework unit-testing asp.net-mvc-4

在我们使用基于Music Store Tutorial的Entity Framework 4.0的MVC4应用程序中,我们使用Moq来模拟DbContext,并且单元测试是逻辑。我们的一种方法证明难以测试,因为它使用HttpContextHttpContextBase。一个示例方法如下所示:

public static ShoppingCart GetCart(HttpContextBase context)
    {
        var cart = new ShoppingCart();
        cart.ShoppingCartId = cart.GetCartId(context);
        return cart;
    }

HttpContextBase收集的唯一属性是[CartSessionKey],如下所示:

public string GetCartId(HttpContextBase context)
{
    if (context.Session[CartSessionKey] == null)
    {
        if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
        {
            context.Session[CartSessionKey] =
                context.User.Identity.Name;
        }
        else
        {
            // Generate a new random GUID using System.Guid class
            Guid tempCartId = Guid.NewGuid();
            // Send tempCartId back to client as a cookie
            context.Session[CartSessionKey] = tempCartId.ToString();
        }
    }
    return context.Session[CartSessionKey].ToString();
}

我们听过恐怖故事,HttpContext是一个非常复杂的课程,如果你打印它,你就有足够的纸张绕地球圈八次。

然而我们想要嘲笑它。问题是如何。我们要模拟的属性是[CartSessionKey],以及来自上下文的属性contest.User.Identity.Name

我们怀疑我们需要使用这样的东西:

        var mockData = new Mock<FakeContext>();
        mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
        mockData.Setup(m => m.Carts).Returns(memoryCartItems);

        Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
        Mock<HttpRequestBase> mockHttpRequest = new Mock<HttpRequestBase>();

        mockHttpRequest.Setup(x => x.CartSessionKey).Returns(1);
        mockHttpContext.Setup(x => x.Request).Returns(mockHttpRequest.Object);

但是我们找不到如何具体实现这一点,因此我们不会对使用context.Session[CartSessionKey]context.User.Identity.Name的方法产生任何错误。

我们希望有人可以帮助我们。

/编辑

当我们这样做时:

 var memoryUserItems = new FakeDbSet<User>()
        {
            new User { Email = "test@test.de",
                       FullName = "Test Person",
                       isAvailable = true,
                       Name = "WHat"
            },
            new User { Email = "test2@test.de",
                       FullName = "Test Person 2",
                       isAvailable = true,
                       Name = "WHat 2"
            }
        };
 (...) Other memory...Items

然后这个:

        // Create mock units of work
        var mockData = new Mock<FakeContext>();
        mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
        mockData.Setup(m => m.Carts).Returns(memoryCartItems);
        mockData.Setup(m => m.Users).Returns(memoryUserItems);

        var principalMock = new Mock<IPrincipal>();
        var identityMock = new Mock<IIdentity>();
        var userMock = 
        identityMock.Setup(x => x.Name).Returns("Test!");
        identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
        mockData.Setup(x => x.Identity).Returns(identityMock.Object);
        var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
        var httpContextBase = new Mock<HttpContextBase>();

        httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
        httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
        httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);

我们收到错误:

  

错误3'project.Models.FakeContext'确实如此   不包含'Identity'的定义,也没有扩展方法   '身份'接受第一个类型的参数   'project.Models.FakeContext'可以找到   (你错过了使用指令或程序集吗?   引用?)

/ edit2

使其更清晰。我正在测试的实际方法如下:

    public ActionResult Complete(int id)
    {
        // Make sure that user is currentuser and otherwise bring user to our Thief page
        if (id != db.GetCurrentUserId())
        {
            return View("Thief");
        }

        var cart = ShoppingCart.GetCart(this.HttpContext);
        var currentDate = DateTime.Today;
        var viewModel = new ShoppingCartViewModel
        {
            CartItems = cart.GetCartItems(),
            CartTotal = cart.GetTotal(),
            ProductItems = db.Products.ToList()
        };

        if (viewModel.CartItems.Count() == 0)
        {
            return View("Empty");
        }

        // Try to write cart to order table
        try
        {
            foreach (var item in viewModel.CartItems)
            {
                ProcessOrder(item, id, currentDate);

            }
            // after this we empty the shopping cart
            cart.EmptyCart();
            return View();
        }
        catch
        {
            // Invalid - display error page
            return View("Error");
        }

    }

可以看出var cart = ShoppingCart.GetCart(this.HttpContext);使用了this.HttpContext。在测试中我只做controller.Complete(1)。我想不能将新的HttpContext传递给控制器​​?

/编辑3

使用下面的代码与模拟时,我收到以下消息:

Test Name:  TestCheckoutCompleteShouldWithEmptyCart
Test FullName:  Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart
Test Source:    Controllers\CheckoutControllerTest.cs : line 141
Test Outcome:   Failed
Test Duration:  0:00:00.0158591

Result Message: 
Test method Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart threw exception: 
System.NullReferenceException: Object reference not set to an instance of an object.
Result StackTrace:  
at Models\ShoppingCart.cs:line 170
   at \Models\ShoppingCart.cs:line 20
   at \Controllers\CheckoutController.cs:line 48
   at Controllers\CheckoutControllerTest.cs:line 143

2 个答案:

答案 0 :(得分:2)

好的,就在这里。以下适用于带有AD的MVC5,我不确定它是否完全向后兼容,你必须检查。

var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
userMock.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();

httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);

答案 1 :(得分:1)

这将帮助您使用Moq编写正确的单元测试。

[TestClass]
public class SutTest
{
    [TestMethod]
    public void GetCartId_WhenUserNameIsNotNull_SessionContainsUserName()
    {
        var httpContextStub = new Mock<HttpContextBase>();
        var httpSessionStub = new Mock<ISessionSettings>();
        httpSessionStub.Setup(x => x.Get<string>(It.IsAny<string>())).Returns(() => null);

        httpSessionStub.SetupSequence(x => x.Get<string>(It.IsAny<string>()))
            .Returns(null)
            .Returns("FakeName");

        var httpUserStub = new Mock<IPrincipal>();
        var httpIdenttyStub = new Mock<IIdentity>();
        httpUserStub.SetupGet(x => x.Identity).Returns(httpIdenttyStub.Object);
        httpIdenttyStub.SetupGet(x => x.Name).Returns("FakeName");

        httpContextStub.Setup(x => x.User).Returns(httpUserStub.Object);
        var sut = new Sut(httpSessionStub.Object);

        var result = sut.GetCartId(httpContextStub.Object);

        Assert.AreEqual("FakeName",result );
    }
}

检查SetupSequence方法,该方法可让您在相同的存根调用中找到对不同值的控制。 同样重要的是将会话与HttpContext分离,因为总是会遇到问题。

public class SessionSettings : ISessionSettings
{
    private readonly HttpSessionStateBase _session;
    public SessionSettings(HttpSessionStateBase session)
    {
        _session = session;
    }

    public T Get<T>(string key)
    {
        return (T)_session[key];
    }

    public void Set<T>(string key, T value)
    {
        _session[key] = value;
    }
}

public interface ISessionSettings
{
    T Get<T>(string key);
    void Set<T>(string key, T value);
}

public class Sut
{
    private ISessionSettings _sessionSettings;
    public Sut(ISessionSettings sessionSettings)
    {
        _sessionSettings = sessionSettings;
    }
    public string GetCartId(HttpContextBase context)
    {
        if (_sessionSettings.Get<string>(CartSessionKey) == null)
        {
            if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
            {
                _sessionSettings.Set<string>(CartSessionKey, context.User.Identity.Name);
            }
            else
            {
                // Generate a new random GUID using System.Guid class
                Guid tempCartId = Guid.NewGuid();
                // Send tempCartId back to client as a cookie
                _sessionSettings.Set<string>(CartSessionKey, tempCartId.ToString());
            }
        }
        return _sessionSettings.Get<string>(CartSessionKey);
    }

    private string CartSessionKey = "key";
}

这样代码更易读,更容易理解。