【发布时间】: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