2

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

2 Answers 2

5

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

Sign up to request clarification or add additional context in comments.

2 Comments

i am facing 2 problem 1- From where BasePath is coming 2- What is the Assembley name of MockHttpSession(); ?
Sorry I forgot a couple things in the demo. I have integrated the answer. The BasePath is a simple variable I use to store the files. Now everything should works
3

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"]);
    }
}

3 Comments

Would be very good if you show some thing with example for me and even it would help future readers
@tdaragon unfortunately MVC development team made controller depend directly from UrlHelper class. Which depends directly from RequestContext. I wish it were interfaces, but team made this strange decision.
@lazyberezovsky - you're right. That is too bad, Controller and UrlHelper are so tightly coupled. I have edited my answer and provided some code for presentation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.