我正在尝试使用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
。
我的嘲笑有什么问题?
答案 0 :(得分:5)
我强烈建议您查看使用Rhino.Mocks
的{{3}},并提供一种优雅的方法来测试您的控制器。以下是您的测试的样子:
[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();
}
}
这是MVCContrib.TestHelper的unit test,是我写的controller的一部分。
答案 1 :(得分: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容器就可以使用该接口来匹配依赖项与实现。)
答案 2 :(得分:0)
我认为问题在于你需要存根整个属性链,或者至少传递给你的ControllerContext Mock一个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假设它为null。
我没有使用Darin描述的解决方案,但它看起来会让你的生活更轻松!