Unit testing controller using MOQ . How to mock httpcontext

12,468

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

Share:
12,468
Ancient
Author by

Ancient

Anything i loves or hate totally depends on my mood , working as a software engineer

Updated on June 28, 2022

Comments

  • Ancient
    Ancient almost 2 years

    I am trying to test my Account controller by using Moq here is what i have done

    Controller

       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 in Redirect 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