我目前正在开发一个MVC4项目。当我做了一些重构时,测试也应该改变。
在第一种情况下,必须传递包含在URL中的会话哈希。那个被禁止的,我决定将它作为Request标头传递给服务器。
示例:
[HttpPost]
public ActionResult Generate(ClipGenerateRequest request)
{
string hash = Request.Headers["hash"];
ClipGenerationResponse responseModel = new ClipGenerationResponse();
return Json(responseModel, JsonRequestBehavior.AllowGet);
}
现在的问题似乎是我无法将Request对象模拟为自定义对象,因为Request对象是一个只读对象。动态设置标头不起作用,因为在执行单元测试时Request为null。所以以下内容不起作用:
[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());
}
使用Moq模拟创建this.requestBase。
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";
所以,我想知道如何解决这个问题很好。我总是可以在构造函数中获取Request,设置一个类变量来保存Request,然后模拟getter / setter以进行测试,但我宁愿使用更好的方法来实现它。虽然我似乎不知道如何让它发挥作用。
PS:请注意,某些类名可能已被更改以供预览。
由于你似乎无法模拟HttpContext.Current.Request,我决定模拟HttpContext.Current。导致:
container.RegisterInstance<HttpRequest>(HttpContext.Current.Request);
可悲的是,这适用于API,但不适用于单元测试,因为HttpContext不能被模拟,因为它不是一个接口。
初始化方法 SomeApiTest.Controllers.LoginControllerTest.Initialize 扔了例外。 System.NotSupportedException: System.NotSupportedException:类型为mock必须是接口或 抽象或非密封的课程。
建议的方法是:
container.RegisterInstance<HttpRequestBase>(HttpContext.Current.Request);
但这不起作用,因为请求无法强制转换为HttpRequestBase。
这意味着,我现在有办法对我的代码进行单元测试,但它将无法再运行..
测试是否可以使用HttpRequestWrapper解决此问题。
看起来以下内容适用于测试:
HttpRequestBase requestBase = new HttpRequestWrapper(HttpContext.Current.Request);
container.RegisterInstance<HttpRequestBase>(requestBase);
但不适合运行时。因为: - 不发送其他标头,例如:UserHostAddress,Custom Headers
使用Postman设置每个请求一个自定义标头,名为“hash”。使用此方法,看起来不再设置这些标头。
看起来在调用方法时设置了标头,但在创建Controller本身时却没有。因此,对此的依赖注入可能不合适。
丑陋的临时修复:
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());
}
答案 0 :(得分:1)
当你在Controller中引用静态类或控制器属性的东西时,你通常会自己开机,因为你的单元测试不能测试你的类,就像你的情况一样。为避免这种情况,您的Controller会使用IoC注入HttpRequestBase。例如,在注册模块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))
);
在单元测试中创建Controller时,非常容易模拟HttpRequestBase,因为它的所有属性以及Headers都是虚拟的。
这是我在项目中经常使用的方法。但是,即使不重写原始代码,也可以选择模拟Controller Request属性。它涉及使用Controller ControllerContext属性:
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();
答案 1 :(得分:0)
我的模拟请求看起来:
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并行,它完美无缺。