使用MOQ的单元测试控制器。如何模拟httpcontext

时间:2013-06-17 06:51:03

标签: asp.net-mvc unit-testing asp.net-mvc-4 moq specflow

我正在尝试使用Moq来测试我的帐户控制器。这就是我所做的

控制器

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

但我的测试失败了,如果在Url.IsLocal方法中来Redirect to Local,它会给予考验。所以我认为这应该模拟我的httpcontextbase和httpcontextrequestbase。

但不知道如何嘲笑。

提前致谢

2 个答案:

答案 0 :(得分:5)

你应该嘲笑HttpContext。我写了这个助手来做这种事情

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

并且在测试方法中你可以像那样使用它

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

希望有所帮助

答案 1 :(得分:3)

在这个特定问题中,您只需使用模拟的Url类覆盖控制器的UrlHelper属性。

对于HttpContext模拟,最好将HttpContextBase注入您的控制器并配置您的DI容器以便为您提供正确的容器。稍后为了测试目的,它会很容易嘲笑它。我相信Autofac有一些自动方式来配置与ASP.NET相关的类的容器,如HttpContextBase

修改

似乎你无法用Moq模拟UrlHelper,正如@lazyberezovsky写的那样 - 你只能模拟接口和虚拟方法。但它并不能阻止你编写自己的模拟对象。这是真的,你需要模拟HttpContext,因为UrlHelper构造函数需要它(实际上,它是RequestContext构造函数所需的,UrlHelper构造函数需要它)...此外,IsLocalUrl不使用上下文中的任何内容,因此您无需提供任何其他设置。

示例代码如下所示:

<强>控制器

public ActionResult Foo(string url)
{
    if (Url.IsLocalUrl(url))
    {
        return Redirect(url);
    }

    return RedirectToAction("Index", "Home");
}

<强>测试

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