2010-06-20 209 views
4

我想用Rhino.Mocks來模擬ControllerContext對象,以便在我的控制器單元測試中訪問像User,Request,Response和Session這樣的運行時對象。我已經寫了下面的方法來模擬控制器。如何使用Rhino.Mocks來模擬ControllerContext

private TestController CreateTestControllerAs(string userName) 
{ 
    var mock = MockRepository.GenerateStub<ControllerContext>(); 
    mock.Stub(con => 
     con.HttpContext.User.Identity.Name).Return(userName); 
    mock.Stub(con => 
     con.HttpContext.Request.IsAuthenticated).Return(true); 

    var controller = CreateTestController(); // left out of example for brevity 
    controller.ControllerContext = mock; 

    return controller; 
} 

但是,我嘲笑ControllerContext的HttpContext爲空,有我嘗試訪問HttpContext.User等引起System.NullReferenceException

我在做什麼錯誤與我的嘲笑?

回答

5

我強烈建議您查看MVCContrib.TestHelper,它使用Rhino.Mocks並提供了一種優雅的方式來測試您的控制器。這是你的測試可能會什麼樣子:

[TestClass] 
public class UsersControllerTests : TestControllerBuilder 
{ 
    [TestMethod] 
    public void UsersController_Index() 
    { 
     // arrange 
     // TODO : this initialization part should be externalized 
     // so that it can be reused by other tests 
     var sut = new HomeController(); 
     this.InitializeController(sut); 
     // At this point sut.Request, sut.Response, sut.Session, ... are 
     // stubed objects on which you could define expectations. 

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

     // assert 
     actual.AssertViewRendered(); 
    } 
} 

這裏是一個unit testcontroller這是一個sample ASP.NET MVC application我寫的一部分。

+0

這工作得很好。仍然必須將一個對象放在'HttpContext.User'屬性中,但這很容易通過執行:'HttpContext.User = new GenericPrincipal(new GenericIdentity(loginName),null);' – ahsteele 2010-06-21 03:16:49

0

我認爲,問題是,你需要存根屬性的整條產業鏈,或者至少傳遞到您的ControllerContext模擬一個的HttpContext,即沿着線的東西:

private TestController CreateTestControllerAs(string userName) 
{ 
    var mock = MockRepository.GenerateStub<ControllerContext>(); 
    var context = MockRepository.GenerateStub<IHttpContext>();  
    mock.Stub(con => 
     con.HttpContext).Return(context); 
    // etc... with User, Identity ... 

    return controller; 
} 

在代碼中,給出你從來沒有專門設置HttpContext爲任何東西,默認情況下你的Stub假定它是空的。

我還沒有使用Darin描述的解決方案,但它看起來會讓你的生活變得更容易!

1

其他答案已經顯示你如何嘲笑一個財產鏈來解決你的問題。

但是,真正的問題在於,如果你違反了law of demeter,單元測試和嘲諷並不能很好地工作。如果你希望你的代碼是可測試的並且是最大可重用的,那麼你需要直接注入代碼的真實依賴關係,並在抽象背後隱藏這些依賴關係。

例如,而不是這樣做:

public class MyClass 
{ 
    public ControllerContext Context { get; set; } 

    public void DoSomething() 
    { 
     // BAD: we're only interested in the name, but instead we've injected 
     // a ControllerContext that can give us a HttpContext that can give us 
     // a User that can give us an Identity that can give us the Name. 
     string name = Context.HttpContext.User.Identity.Name; 
     // etcetera 
    } 
} 

這樣做:

public class MyClass 
{ 
    public INameProvider NameProvider { get; set; } 

    public void DoSomething() 
    { 
     // GOOD: we've injected a name provider 
     string name = NameProvider.Name; 
     // etcetera 
    } 
} 

通過引入INameProvider概念,組件代碼,測試和模擬變得更簡單。您的代碼也變得更加可重用:它只依賴於「名稱提供者」的抽象概念,而不是一堆ASP.NET類。只要有可能實現INameProvider適配器,您就可以在任何環境中重用您的組件。

權衡是你需要聲明INameProvider接口並編寫一個實現它的包裝類。當你一貫地遵循這種方法時,你會得到很多小的接口和適配器類。這就是測試驅動開發的方式。 (如果你想知道爲什麼我引入INameProvider而不是直接設置名稱 - 這是爲了讓IoC容器可以使用接口來匹配依賴和實現。)

+0

這是一種常用方法,但會導致用自定義代碼複製mvc層。即用戶的INameProvider,控制器的服務類,會話... Cookie,請求,響應以及更多的抽象概率是動作結果。 – 2015-09-25 19:26:39

相關問題