Unit testing controller using MOQ . How to mock httpcontext
Solution 1
In this particular issue you can simply overwrite controller's Url
property with mocked UrlHelper
class.
For HttpContext
mocking, it might be good to inject HttpContextBase
to your controller and configure your DI container to serve the proper one for you. It would ease mocking it later for testing purposes. I believe Autofac has some automatic way to configure container for ASP.NET-related classes like HttpContextBase
.
EDIT
It seems you can't mock UrlHelper
with Moq, as @lazyberezovsky wrote - you can mock only interfaces and virtual methods. But it does not stop you from writing your own mocked object. That's true you need to mock HttpContext
, as it's required by UrlHelper
constructor (actually, it's required by RequestContext
constructor, which is required by UrlHelper
constructor)... Moreover, IsLocalUrl
does not use anything from context, so you do not have to provide any additional setup.
Sample code would look like that:
Controller:
public ActionResult Foo(string url)
{
if (Url.IsLocalUrl(url))
{
return Redirect(url);
}
return RedirectToAction("Index", "Home");
}
Tests:
[TestClass]
public class HomeControllerTests
{
private Mock<HttpContextBase> _contextMock;
private UrlHelper _urlHelperMock;
public HomeControllerTests()
{
_contextMock = new Mock<HttpContextBase>();
_urlHelperMock = new UrlHelper(new RequestContext(_contextMock.Object, new RouteData()));
}
[TestMethod]
public void LocalUrlTest()
{
HomeController controller = new HomeController();
controller.Url = _urlHelperMock;
RedirectResult result = (RedirectResult)controller.Foo("/");
Assert.AreEqual("/", result.Url);
}
[TestMethod]
public void ExternalUrlTest()
{
HomeController controller = new HomeController();
controller.Url = _urlHelperMock;
RedirectToRouteResult result = (RedirectToRouteResult)controller.Foo("http://test.com/SomeUrl");
Assert.AreEqual("Index", result.RouteValues["action"]);
Assert.AreEqual("Home", result.RouteValues["controller"]);
}
}
Solution 2
You should mock the HttpContext
. I wrote this helper to do this kind of things
public static Mock<HttpContextBase> MockControllerContext(bool authenticated, bool isAjaxRequest)
{
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.HttpMethod).Returns("GET");
request.SetupGet(r => r.IsAuthenticated).Returns(authenticated);
request.SetupGet(r => r.ApplicationPath).Returns("/");
request.SetupGet(r => r.ServerVariables).Returns((NameValueCollection)null);
request.SetupGet(r => r.Url).Returns(new Uri("http://localhost/app", UriKind.Absolute));
if (isAjaxRequest)
request.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection { { "X-Requested-With", "XMLHttpRequest" } });
var server = new Mock<HttpServerUtilityBase>();
server.Setup(x => x.MapPath(It.IsAny<string>())).Returns(BasePath);
var response = new Mock<HttpResponseBase>();
response.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())).Returns((String url) => url);
var session = new MockHttpSession();
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Setup(c => c.Request).Returns(request.Object);
mockHttpContext.Setup(c => c.Response).Returns(response.Object);
mockHttpContext.Setup(c => c.Server).Returns(server.Object);
mockHttpContext.Setup(x => x.Session).Returns(session);
return mockHttpContext;
}
public class MockHttpSession : HttpSessionStateBase
{
private readonly Dictionary<string, object> sessionStorage = new Dictionary<string, object>();
public override object this[string name]
{
get { return sessionStorage.ContainsKey(name) ? sessionStorage[name] : null; }
set { sessionStorage[name] = value; }
}
public override void Remove(string name)
{
sessionStorage.Remove(name);
}
}
and in a test method you use it like that
private AccountController GetController(bool authenticated)
{
var requestContext = new RequestContext(Evoltel.BeniRosa.Web.Frontend.Tests.Utilities.MockControllerContext(authenticated, false).Object, new RouteData());
var controller = new CofaniController(cofaniRepository.Object, categorieRepository.Object, emailService.Object, usersService.Object)
{
Url = new UrlHelper(requestContext)
};
controller.ControllerContext = new ControllerContext()
{
Controller = controller,
RequestContext = requestContext
};
return controller;
}
[Test]
public void LogOn_Post_ReturnsRedirectOnSuccess_WithoutReturnUrl()
{
AccountController controller = GetController(false);
var httpContext = Utilities.MockControllerContext(false, false).Object;
controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller);
LogOnModel model = new LogOnModel()
{
UserName = "someUser",
Password = "goodPassword",
RememberMe = false
};
ActionResult result = controller.LogOn(model, null);
Assert.IsInstanceOf(typeof(RedirectToRouteResult), result);
RedirectToRouteResult redirectResult = (RedirectToRouteResult)result;
Assert.AreEqual("Home", redirectResult.RouteValues["controller"]);
Assert.AreEqual("Index", redirectResult.RouteValues["action"]);
}
Hope it helps
Ancient
Anything i loves or hate totally depends on my mood , working as a software engineer
Updated on June 28, 2022Comments
-
Ancient almost 2 years
I am trying to test my Account controller by using
Moq
here is what i have doneController
private readonly IWebSecurity _webSecurity; public AccountController(IWebSecurity webSecurity) { this._webSecurity = webSecurity; } public ActionResult Login(LoginModel model, string returnUrl) { if (ModelState.IsValid && _webSecurity.login(model)) { return RedirectToLocal(returnUrl); } // If we got this far, something failed, redisplay form ModelState.AddModelError("", "The user name or password provided is incorrect."); return View(model); } private ActionResult RedirectToLocal(string returnUrl) { if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "Home"); } }
IWebSecurity
public interface IWebSecurity { bool login(LoginModel model); } public class WebSecurity : IWebSecurity { public bool login(LoginModel model) { return WebMatrix.WebData.WebSecurity.Login(model.UserName, model.Password, model.RememberMe); } }
MyTestClass
[AfterScenario] public void OnAfterScenario() { mockRepository.VerifyAll(); } LoginModel loginModel; AccountController _controller; #region Initializing Mock Repository readonly Mock<IWebSecurity> mockRepository = new Mock<IWebSecurity>(MockBehavior.Loose); ViewResult viewResult; #endregion [Given] public void Given_Account_controller() { _controller = new AccountController(mockRepository.Object); } [When] public void When_login_is_called_with_LoginModel(Table table) { loginModel = new LoginModel { UserName = table.Rows[0][1], Password = table.Rows[1][1] }; mockRepository.Setup(x => x.login(loginModel)).Returns(true); viewResult = (ViewResult)_controller.Login(loginModel, "/"); } [Then] public void Then_it_should_validate_LoginModel() { Assert.IsTrue(_controller.ModelState.IsValid); } [Then] public void Then_it_should_return_default_view() { Assert.AreEqual(viewResult.ViewName, "Index"); }
But my test is failing and its giving expection when if come to
Url.IsLocal
inRedirect to Local
method . so i think here is should mock my httpcontextbase and httpcontextrequestbase .But don't know how to mock that .
Thanks in advance