2013-12-16 38 views
1

在我們的基於Music Store Tutorial的Entity Framework 4.0的MVC4應用程序中,我們使用Moq來模擬DbContext,單元測試是邏輯。我們的一種方法很難測試,因爲它使用了HttpContextHttpContextBase。一個示例方法是這樣的:如何在ShoppingCart控制器/模型中模擬HttpContext

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 = "[email protected]", 
         FullName = "Test Person", 
         isAvailable = true, 
         Name = "WHat" 
      }, 
      new User { Email = "[email protected]", 
         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「項目。 Models.FakeContext'確實不是 包含「身份」和沒有擴展方法 「身份」接受 類型「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 
+0

您是否正在測試公共字符串GetCartId(HttpContextBase上下文)方法?你的MUT是什麼? GetCart或GetCartId? – Spock

+0

Hi @Spock Hi,什麼是MUT? 'GetCartId'是GetCart依賴的主要關注點。 –

+0

對不起,這是測試方法。你試圖達到的目標非常簡單。看到我的答案,還有其他幾種做法。樂於幫助 – Spock

回答

2

OK,這裏去。下面在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); 
+0

謝謝。我編輯了一個與userMock對象有關的新問題。你能解釋一下如何使x.Identity部分工作嗎? –

+0

@ user2609980你的'FakeContext'看起來像什麼?它使用'IIdentity'嗎? –

+0

它由用於每個'Model'(例如'public virtual IDbSet Products {get; set;}'的公共虛擬IDbSet'和兩個空方法如public int SaveChanges() { return 1; } '和 '公共無效的Dispose(){ 扔 新NotImplementedException();} ?'所以,它不使用'IIdentity'我們應該 –

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"; 
} 

這樣代碼更具可讀性,更易於理解。