因此,控制器上下文取决于一些asp.net内部。 有什么方法可以完全模拟这些以进行单元测试? 当我仅需要例如Request.HttpMethod返回" GET"时,似乎很容易用大量设置阻塞测试。
我已经在网上看到了一些例子/帮助者,但其中有些是过时的。 认为这将是保持最新和最佳状态的好地方。
我正在使用最新版本的犀牛模拟
使用最小起订量看起来像这样:
1 2 3 4 5 6
| var request = new Mock<HttpRequestBase>();
request.Expect(r => r.HttpMethod).Returns("GET");
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Expect(c => c.Request).Returns(request.Object);
var controllerContext = new ControllerContext(mockHttpContext.Object
, new RouteData(), new Mock<ControllerBase>().Object); |
我认为Rhino Mocks的语法相似。
这是使用MsTest和Moq的示例单元测试类,该类模拟HttpRequest和HttpResponse对象。 (.NET 4.0,ASP.NET MVC 3.0)
控制器动作从请求中获取值,并在响应对象中设置http标头。其他http上下文对象可以通过类似的方式进行模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| [TestClass]
public class MyControllerTest
{
protected Mock<HttpContextBase> HttpContextBaseMock;
protected Mock<HttpRequestBase> HttpRequestMock;
protected Mock<HttpResponseBase> HttpResponseMock;
[TestInitialize]
public void TestInitialize()
{
HttpContextBaseMock = new Mock<HttpContextBase>();
HttpRequestMock = new Mock<HttpRequestBase>();
HttpResponseMock = new Mock<HttpResponseBase>();
HttpContextBaseMock.SetupGet(x => x.Request).Returns(HttpRequestMock.Object);
HttpContextBaseMock.SetupGet(x => x.Response).Returns(HttpResponseMock.Object);
}
protected MyController SetupController()
{
var routes = new RouteCollection();
var controller = new MyController();
controller.ControllerContext = new ControllerContext(HttpContextBaseMock.Object, new RouteData(), controller);
controller.Url = new UrlHelper(new RequestContext(HttpContextBaseMock.Object, new RouteData()), routes);
return controller;
}
[TestMethod]
public void IndexTest()
{
HttpRequestMock.Setup(x => x["x"]).Returns("1");
HttpResponseMock.Setup(x => x.AddHeader("name","value"));
var controller = SetupController();
var result = controller.Index();
Assert.AreEqual("1", result.Content);
HttpRequestMock.VerifyAll();
HttpResponseMock.VerifyAll();
}
}
public class MyController : Controller
{
public ContentResult Index()
{
var x = Request["x"];
Response.AddHeader("name","value");
return Content(x);
}
} |
这是Jason链接的摘录。与Phil的方法相同,但使用犀牛。
注意:在嘲笑了mockRequest的内部之前,将嘲笑mockHttpContext.Request返回mockRequest。我相信此命令是必需的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // create a fake web context
var mockHttpContext = MockRepository.GenerateMock<HttpContextBase>();
var mockRequest = MockRepository.GenerateMock<HttpRequestBase>();
mockHttpContext.Stub(x => x.Request).Return(mockRequest);
// tell the mock to return"GET" when HttpMethod is called
mockRequest.Stub(x => x.HttpMethod).Return("GET");
var controller = new AccountController();
// assign the fake context
var context = new ControllerContext(mockHttpContext,
new RouteData(),
controller);
controller.ControllerContext = context;
// act
... |
在MVC2中,此过程似乎已稍有更改(我正在使用RC1)。如果操作需要特定的方法([HttpPost],[HttpGet]),Phil Haack的解决方案对我不起作用。在Reflector中徘徊,看起来验证这些属性的方法已更改。 MVC现在检查request.Headers,request.Form和request.QueryString的X-HTTP-Method-Override值。
如果您为这些属性添加模拟,它将起作用:
1 2 3 4 5 6 7 8 9
| var request = new Mock<HttpRequestBase>();
request.Setup(r => r.HttpMethod).Returns("POST");
request.Setup(r => r.Headers).Returns(new NameValueCollection());
request.Setup(r => r.Form).Returns(new NameValueCollection());
request.Setup(r => r.QueryString).Returns(new NameValueCollection());
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Expect(c => c.Request).Returns(request.Object);
var controllerContext = new ControllerContext(mockHttpContext.Object, new RouteData(), new Mock<ControllerBase>().Object); |
或者,您可以使用Typemock隔离器执行此操作,而无需完全发送假控制器:
1
| Isolate.WhenCalled(()=>HttpContext.Request.HttpMethod).WillReturn("Get"); |
我已经完成了这个规格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public abstract class Specification <C> where C: Controller
{
protected C controller;
HttpContextBase mockHttpContext;
HttpRequestBase mockRequest;
protected Exception ExceptionThrown { get; private set; }
[SetUp]
public void Setup()
{
mockHttpContext = MockRepository.GenerateMock<HttpContextBase>();
mockRequest = MockRepository.GenerateMock<HttpRequestBase>();
mockHttpContext.Stub(x => x.Request).Return(mockRequest);
mockRequest.Stub(x => x.HttpMethod).Return("GET");
EstablishContext();
SetHttpContext();
try
{
When();
}
catch (Exception exc)
{
ExceptionThrown = exc;
}
}
protected void SetHttpContext()
{
var context = new ControllerContext(mockHttpContext, new RouteData(), controller);
controller.ControllerContext = context;
}
protected T Mock< T >() where T: class
{
return MockRepository.GenerateMock< T >();
}
protected abstract void EstablishContext();
protected abstract void When();
[TearDown]
public virtual void TearDown()
{
}
} |
果汁在这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| [TestFixture]
public class When_invoking_ManageUsersControllers_Update :Specification <ManageUsersController>
{
private IUserRepository userRepository;
FormCollection form;
ActionResult result;
User retUser;
protected override void EstablishContext()
{
userRepository = Mock<IUserRepository>();
controller = new ManageUsersController(userRepository);
retUser = new User();
userRepository.Expect(x => x.GetById(5)).Return(retUser);
userRepository.Expect(x => x.Update(retUser));
form = new FormCollection();
form["IdUser"] = 5.ToString();
form["Name"] = 5.ToString();
form["Surename"] = 5.ToString();
form["Login"] = 5.ToString();
form["Password"] = 5.ToString();
}
protected override void When()
{
result = controller.Edit(5, form);
}
[Test]
public void is_retrieved_before_update_original_user()
{
userRepository.AssertWasCalled(x => x.GetById(5));
userRepository.AssertWasCalled(x => x.Update(retUser));
}
} |
请享用
我发现冗长的模拟程序会产生太多摩擦。
我们发现的最好方法-在实际项目中使用ASP.NET MVC-是将HttpContext抽象到简单地通过的IWebContext接口。然后,您可以轻松模拟IWebContext。
这是一个例子