【问题标题】:MVC4 / Mocking Controller.RequestMVC4 / 模拟 Controller.Request
【发布时间】:2015-04-09 14:57:37
【问题描述】:

我目前正在从事 MVC4 项目。正如我做了一些重构,测试也应该改变。

在第一种情况下,必须在 URL 中传递会话哈希。既然如此,我决定将它作为请求标头传递给服务器。

例子:

[HttpPost]
public ActionResult Generate(ClipGenerateRequest request)
{
    string hash = Request.Headers["hash"];

    ClipGenerationResponse responseModel = new ClipGenerationResponse();            

    return Json(responseModel, JsonRequestBehavior.AllowGet);
}

现在的问题似乎是我无法将 Request 对象模拟为自定义对象,因为 Request 对象是只读对象。动态设置标头不起作用,因为执行单元测试时请求为空。所以以下方法不起作用:

[TestMethod]
public void GenerateTest()
{
    GenerationController target = new GenerationController(this.loginModelMock, this.clipTemplateModelMock, this.clipTemplateFieldModelMock);
    target.Request = this.requestBase;
    string templateId = "0";

    ActionResult actual = target.Generate(templateId);
    Assert.AreEqual(typeof(JsonResult), actual.GetType());
    Assert.AreEqual(typeof(ClipGenerationResponse), ((JsonResult)actual).Data.GetType());
}

this.requestBase 将使用 Moq 模拟创建。

public HttpContextBase CreateMockHttpContext()
{
    var serverVariables = new NameValueCollection {
        { "UserHostAddress", "127.0.0.1" },
        { "UserAgent", "Unit Test Value" }
    };

    var httpRequest = new Moq.Mock<HttpRequestBase>();
    httpRequest.SetupGet(x => x.Headers).Returns(
        new System.Net.WebHeaderCollection {
            {"hash", "somehash"}
        }
    );

    httpRequest.Setup(x => x.ServerVariables.Get(It.IsAny<string>()))
        .Returns<string>(x =>
            {
                return serverVariables[x];
            });

    var httpContext = (new Moq.Mock<HttpContextBase>());
    httpContext.Setup(x => x.Request).Returns(httpRequest.Object);

    return httpContext.Object;
}

静态方式也行不通:

target.Request.Headers["hash"] = "hash";

所以,我想知道如何很好地解决这个问题。我总是可以在构造函数中获取请求,设置一个类变量来保存请求,然后为了测试目的模拟 getter/setter,但我宁愿使用更好的方法来做。虽然我似乎不知道如何让它工作。

PS:请注意,某些类名可能已更改以供预览。

更新

由于您似乎无法模拟 HttpContext.Current.Request,我决定模拟 HttpContext.Current。结果:

container.RegisterInstance<HttpRequest>(HttpContext.Current.Request);

遗憾的是,这适用于 API,但不适用于单元测试,因为 HttpContext 不能被模拟,因为它不是接口。

初始化方法 SomeApiTest.Controllers.LoginControllerTest.Initialize 抛出异常。 System.NotSupportedException: System.NotSupportedException:要模拟的类型必须是接口或 抽象或非密封类。 .

建议的方法是:

container.RegisterInstance<HttpRequestBase>(HttpContext.Current.Request);

但这不起作用,因为 Request 无法转换为 HttpRequestBase。

这意味着,我现在确实有办法对我的代码进行单元测试,但它将不再能够运行..


测试是否可以使用 HttpRequestWrapper 解决此问题。


看起来以下内容确实适用于测试:

HttpRequestBase requestBase = new HttpRequestWrapper(HttpContext.Current.Request);
container.RegisterInstance<HttpRequestBase>(requestBase);

但不适用于运行时。因为: - 不发送附加标头,例如:UserHostAddress、自定义标头

使用 Postman 为每个请求设置一个名为“hash”的自定义标头。使用此方法,这些标头似乎不再设置。


看起来像在调用方法时设置了标题,但不是在创建控制器本身时设置的。因此,对此的依赖注入可能不合适。


丑陋的临时修复:

    private AuthenticationHelper authenticationHelper = null;
    private ILoginModel iLoginModel = null;
    private IModuleModel iModuleModel = null;
    private HttpRequestBase iRequestBase = null;

    public LoginController(ILoginModel loginModel, IModuleModel moduleModel, HttpRequestBase requestBase)
    {
        this.authenticationHelper = new AuthenticationHelper(loginModel);
        this.iLoginModel = loginModel;
        this.iModuleModel = moduleModel;
        this.iRequestBase = requestBase;
    }

    private HttpRequestBase GetRequestBase()
    {
        if (Request != null)
        {
            return Request;
        }
        else
        {
            return iRequestBase;
        }
    }

    [HttpPost]
    public ActionResult Login(LoginRequest login)
    {
        var ip = this.authenticationHelper.GetIpAddress(GetRequestBase());
        var userAgent = this.authenticationHelper.GetUserAgent(GetRequestBase());
    }

【问题讨论】:

  • 在模拟中,您也可以将值设置为只读属性.... mockobject.SetupGet(i => i.ReadOnlyProperty).Returns("Return value");也许您可以尝试使用它。

标签: c# asp.net-mvc unit-testing mocking moq


【解决方案1】:

当您在 Controller 中通过静态类或 Controller 属性引用某些东西时,您通常会在启动时自行拍摄,因为您使您的类无法通过单元测试进行测试,就像您的情况一样。为了避免这种情况,您的控制器将 HttpRequestBase 注入 IoC。例如,在您注册模块 AutofacWebTypesModule 后使用 Autofac,您的控制器可能会在其构造函数类型的 HttpRequestBase 参数中接受基本上您使用 Register 属性获得的内容。所以你应该让你的控制器看起来像这样:

public class GenerationController : Controller
{
    readonly HttpRequestBase _request;
    public GenerationController(
            HttpRequestBase request,
            // Additional constructor parameters goes here
        )
    {
        _request = request;
    }
}

使用 Unity,您应该像这样为 HttpRequestBase 注册工厂:

container
    .RegisterType<HttpRequestBase>(
        new InjectionFactory(c => new HttpRequestWrapper(HttpContext.Current.Request))
    );

当您在单元测试中创建控制器时,非常容易模拟 HttpRequestBase,因为它的所有属性以及标头都是虚拟的。

这是我通常在我的项目中使用的方法。但是,即使不重写原始代码,也可以选择模拟控制器请求属性。涉及到ControllerControllerContext属性的使用:

Mock<HttpContextBase> httpContextMock = new Mock<HttpContextBase>();
Mock<HttpRequestBase> httpReguestMock = new Mock<HttpRequestBase>();
httpContextMock.SetupGet(c => c.Request).Returns(httpReguestMock.Object);
GenerationController controller = new GenerationController();

controller.ControllerContext = new ControllerContext(httpContextMock.Object, new RouteData(), controller);
controller.Index();

【讨论】:

  • 我正在使用 Unity 进行依赖注入,但您的建议是将请求添加到构造函数中?
  • 据我所知,Unity as Autofac 默认以构造函数参数的形式提供依赖关系,所以是的,这就是你应该做的。统一制作注册 container.RegisterInstance(HttpContext.Current.Request);现在它可以在创建控制器时成为解析器。在单元测试中,只需传递给从 HttpRequestBase 派生的控制器构造函数 Mock。
  • 对不起,我根本没有使用 Unity,所以我犯了一个错误。您需要在 Unity 中注册 Factory。我编辑了我的答案以包括这个。我检查了它,它工作正常。
  • 真棒它的工作原理,虽然我确实通过了 requestmock 与控制器的创建:]
【解决方案2】:

我的请求外观模拟:

var request = A.Fake<HttpRequestBase>();
var formCollection = new NameValueCollection();
A.CallTo(() => request.HttpMethod).Returns("POST");
A.CallTo(() => request.Headers).Returns(new System.Net.WebHeaderCollection
{
        {"X-Requested-With", "XMLHttpRequest"}
});
A.CallTo(() => request.Form).Returns(formCollection);
A.CallTo(() => request.ApplicationPath).Returns("/");

根据我的需要,它可以工作。我正在使用 FakeItEasy 模拟请求,但我很确定您可以使用 Moq 代替。我在我的项目中并行使用了 FakeItEasy 和 Moq,效果很好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-22
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    • 2012-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多