ASP.NET MVC:单元测试使用UrlHelper的控制器

171
我的一个控制器动作,在 Ajax 请求中被调用,它返回一个 URL 给客户端以便进行重定向。我正在使用 Url.RouteUrl(..),但在我的单元测试中,这会失败,因为 Controller.Url 参数没有被预填充。
我尝试了很多东西,包括尝试存根 UrlHelper(失败了),手动创建具有存根 HttpContextBaseRequestContextUrlHelper (在调用 RouteCollection.GetUrlWithApplicationPath 时失败)。
我已经在谷歌上搜过了,但几乎找不到相关的内容。我在 Controller 动作中使用 Url.RouteUrl 做了某些极其愚蠢的事情吗?有更简单的方法吗?
更糟糕的是,我想在我的单元测试中测试返回的 URL - 实际上,我只关心它是否重定向到正确的路由,但由于我返回的是 URL 而不是路由,所以我想控制解析的 URL(例如,通过使用一个存根 RouteCollection) - 但我会很高兴先让我的测试通过。
3个回答

203

这是我一个测试用例的示例(使用 xUnit + Moq),适用于类似情况(在控制器中使用 Url.RouteUrl)。

var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);

var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
request.SetupGet(x => x.ApplicationPath).Returns("/");
request.SetupGet(x => x.Url).Returns(new Uri("http://localhost/a", UriKind.Absolute));
request.SetupGet(x => x.ServerVariables).Returns(new System.Collections.Specialized.NameValueCollection());

var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
response.Setup(x => x.ApplyAppPathModifier("/post1")).Returns("http://localhost/post1");

var context = new Mock<HttpContextBase>(MockBehavior.Strict);
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);

var controller = new LinkbackController(dbF.Object);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
controller.Url = new UrlHelper(new RequestContext(context.Object, new RouteData()), routes);

2
目前我采用了一种解决方案,其中我对UrlHelper的调用进行了抽象处理,以便我可以拦截它们。感谢你的代码片段,这将节省我大量时间来弄清楚如何正确地模拟请求/响应/ControllerContext。 - efdee
感谢 @eu-ge-ne 的回答,它对我也有很大的帮助。我已经包含了一些更多的 Moq 设置,以使用 UpdateModel 使用的 FormCollection 参数。 - jebcrum
16
太好了,不过有个提示:我将这个用作 MockHelper,并将 response.Setup 对于 ApplyAppPathModifier 更改为以下内容:response.Setup(x => x.ApplyAppPathModifier(Moq.It.IsAny<String>())).Returns((String url) => url); 这样做很丑陋,但我可以获得以 URL 编码形式返回的序列化对象,而不是硬编码返回值。 - eduncan911
部分地对我有用。你有什么想法为什么我得到了Controller/而不是Controller/Action?我的测试失败了,因为它们不太一样,但我注册了相同的路由值。非常奇怪... - Nick
3
ApplyAppPathModifier 部分是 UrlHelper 中非常关键的部分。 - Chris S
如何获取 MvcApplication.RegisterRoutes(routes); - Jeson Martajaya

37

这是从eu-ge-ne修改的实现。该实现根据应用程序中定义的路由返回生成的链接。eu-ge-ne的示例始终返回固定响应。下面的方法将允许您测试是否将正确的操作/控制器和路由信息传递给UrlHelper - 如果您正在测试对UrlHelper的调用,则需要这样做。

var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();

context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session.Object);
context.Setup(ctx => ctx.Server).Returns(server.Object);

request.SetupGet(x => x.ApplicationPath).Returns("/");
request.SetupGet(x => x.Url).Returns(new Uri("http://localhost/a", UriKind.Absolute));
request.SetupGet(x => x.ServerVariables).Returns(new NameValueCollection());

response.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(x => x);

context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);

var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var helper = new UrlHelper(new RequestContext(context.Object, new RouteData()), routes);

2

在@eu-ge-ne的回答基础上,我做了一些补充:

我有一个ActionResult,它既进行了重定向,又使用了一个FormCollection参数进行了UpdateModel调用。为了让UpdateModel()正常工作,我不得不将以下内容添加到我的模拟HttpRequestBase中:

FormCollection collection = new FormCollection();
collection["KeyName"] = "KeyValue";

request.Setup(x => x.Form).Returns(collection);
request.Setup(x => x.QueryString).Returns(new NameValueCollection());

为了测试重定向的URL是否正确,您可以执行以下操作:
RedirectResult result = controller.ActionName(modelToSubmit, collection) as RedirectResult;
Assert.AreEqual("/Expected/URL", result.Url);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接