How to mock the Request.ServerVariables using MOQ for ASP.NET MVC?
i'm just learning to put in unit testing for my asp.net mvc when i came to learn about the mock and the different frameworks there is out there now.
after checking SO, i found that MOQ seems to be the easiest to pick up. as of now i'm stuck trying to mock the Request.ServerVariables, as after reading this post, i've learned that it's better to abstract them into property.
as such:
/// <summary>
/// Return the server port
/// </summary>
protected string ServerPort
{
get
{
return Request.ServerVariables.Get("SERVER_PORT");
}
}
But i'm having a hard time learning how to properly mock this. I have a home controller ActionResult function which grabs the user server information and proceed to create a form to grab the user's information.
i tried to use hanselman's mvcmockhelpers class but i'm not sure how to use it.
this is what i have so far...
[Test]
public void Create_Redirects_To_开发者_如何学编程ProductAdded_On_Success()
{
FakeViewEngine engine = new FakeViewEngine();
HomeController controller = new HomeController();
controller.ViewEngine = engine;
MvcMockHelpers.SetFakeControllerContext(controller);
controller.Create();
var results = controller.Create();
var typedResults = results as RedirectToRouteResult;
Assert.AreEqual("", typedResults.RouteValues["action"], "Wrong action");
Assert.AreEqual("", typedResults.RouteValues["controller"], "Wrong controller");
}
Questions:
- As of now i'm still getting null exception error when i'm running the test. So what am i missing here?
- And if i use the mvcmockhelpers class, how can i still call the request.verifyall function to ensure all the mocking are properly setup?
so basically i've put in all the references and using this function i was able to get it working. i didn't use mvcmockhelpers class yet as i'm still trying to learn all of this.
and for anyone who's interested to see how i solved this, this is the code that i used.
[Test]
public void Create_Returns_ViewResult_On_Success()
{
var server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
request.Setup(x => x.ApplicationPath).Returns("/");
request.Setup(x => x.Url).Returns(new Uri("http://localhost"));
request.Setup(x => x.ServerVariables).Returns(new System.Collections.Specialized.NameValueCollection{
{ "SERVER_NAME", "localhost" },
{ "SCRIPT_NAME", "localhost" },
{ "SERVER_PORT", "80" },
{ "HTTPS", "www.melaos.com" },
{ "REMOTE_ADDR", "127.0.0.1" },
{ "REMOTE_HOST", "127.0.0.1" }
});
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
context.SetupGet(x => x.Server).Returns(server.Object);
var controller = new HomeController();
//MvcMockHelpers.SetFakeControllerContext(controller);
controller.ControllerContext = new ControllerContext(context. Object, new RouteData(), controller);
var results = controller.Create();
Assert.IsNotNull(results);
Assert.IsInstanceOf(typeof(ViewResult), results);
}
Since I want to mock HttpContext, not HttpContextBase, I tried to find a way to convert HttpContextBase mock object to HttpContext and found out that it's impossible to do.
HttpContext
This is the vintage asp.net context. The problem with this is that it has no base class and isn't virtual, and hence is unusable for testing (cannot mock it). It's recommended to not pass it around as function arguments, instead pass around variables of type HttpContextBase.
HttpContextBase
This is the (new to c# 3.5) replacement to HttpContext. Since it is abstract, it is now mockable. The idea is that your functions that expect to be passed a context should expect to receive one of these. It is concretely implemented by HttpContextWrapper
HttpContextWrapper
Also new in C# 3.5 - this is the concrete implementation of HttpContextBase. To create one of these in a normal webpage, use new HttpContextWrapper(HttpContext.Current).
The idea is that to make your code unit-testable, you declare all your variables and function parameters to be of type HttpContextBase, and use an IOC framework eg Castle Windsor to get it injected. In normal code, castle is to inject the equivalent of 'new HttpContextWrapper(HttpContext.Current)', whereas in test code you're to be given a mock of HttpContextBase.
(source: http://www.splinter.com.au/httpcontext-vs-httpcontextbase-vs-httpcontext/)
So I change the way to call my code.
From using HttpContext
:
public static string GetIpAddress(HttpContext currentContext) {
...
}
To use HttpContextBase
:
public static string GetIpAddress(HttpContextBase currentContext) {
...
}
And when I use the method, I wrap HttpContext with HttpContextWrapper like so:
var ip = GeneralUtil.GetIpAddress(new HttpContextWrapper(HttpContext.Current));
Now you can easily mock HttpContextBase by following Dan Atkinson's reply
精彩评论