ASP.NET MVC: kontrolery testów jednostkowych, które używają UrlHelper

170

Jedna z moich akcji kontrolera, która jest wywoływana w żądaniu Ajax, zwraca adres URL po stronie klienta, aby mógł wykonać przekierowanie. Używam Url.RouteUrl(..)i podczas moich testów jednostkowych kończy się to niepowodzeniem, ponieważ Controller.Urlparametr nie jest wstępnie wypełniony.

Próbowałem wielu rzeczy, między innymi próbując odgiąć UrlHelper(co nie powiodło się), ręcznie tworząc UrlHelperz a, RequestContextktóre ma odgałęzienie (co nie HttpContextBasepowiodło się podczas RouteCollection.GetUrlWithApplicationPathpołączenia).

Przeszukałem Google, ale nie znalazłem praktycznie nic na ten temat. Czy robię coś niesamowicie głupiego, używając Url.RouteUrlmojej akcji kontrolera? Czy istnieje prostszy sposób?

Co gorsza, chciałbym móc przetestować zwrócony adres URL w moim teście jednostkowym - w rzeczywistości interesuje mnie tylko to, że przekierowuje na właściwą trasę, ale ponieważ zwracam adres URL zamiast trasy, chciałbym kontrolować adres URL, który jest rozwiązywany (np. za pomocą kodu skróconego RouteCollection) - ale będę szczęśliwy, gdy mój test przejdzie pomyślnie.

efdee
źródło

Odpowiedzi:

202

Oto jeden z moich testów (xUnit + Moq) tylko dla podobnego przypadku (przy użyciu Url.RouteUrl w kontrolerze)

Mam nadzieję że to pomoże:

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);
eu-ge-ne
źródło
2
Na razie wybrałem rozwiązanie, w którym wyodrębniłem wywołania do UrlHelper, aby móc je przechwycić. Dziękuję jednak za Twój fragment kodu, ale zaoszczędzi mi to dużo czasu na zastanawianiu się, jak poprawnie zinterpretować żądanie / odpowiedź / ControllerContext.
efdee
Dzięki za odpowiedź @ eu-ge-ne, bardzo mi to pomogło. Dołączyłem więcej ustawień moq, aby użyć parametru formcollection używanego przez UpdateModel
jebcrum
16
+1 doskonały. Chociaż wskazówka: używam tego jako MockHelpera i zmieniam odpowiedź.Setup for ApplyAppPathModifier na to: response.Setup (x => x.ApplyAppPathModifier (Moq.It.IsAny <String> ())). Returns ((String url ) => url); Jest brzydki, ale otrzymuję zserializowany obiekt z powrotem w postaci zakodowanej w postaci adresu URL, zamiast zakodować na sztywno zwróconą wartość.
eduncan911
To częściowo działa dla mnie. Jakieś pomysły, dlaczego otrzymuję kontroler / zamiast kontrolera / akcji? Mój test kończy się niepowodzeniem, ponieważ nie są one takie same, a mimo to rejestruję te same wartości routingu. Bardzo dziwne ...
Nick
3
Ta ApplyAppPathModifierczęść jest krytyczna dla UrlHelper
Chris S,
37

Zmodyfikowana implementacja z eu-ge-ne. Ten zwraca wygenerowany link na podstawie tras zdefiniowanych w aplikacji. Przykład eu-ge-ne zawsze zwracał stałą odpowiedź. Poniższe podejście pozwoli Ci przetestować, czy prawidłowe działanie / kontroler i informacje o trasie są przekazywane do UrlHelper - co chcesz, jeśli testujesz wywołanie 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);
Steven Pena
źródło
12

Ten wpis może być przydatny, jeśli chcesz mockować klasę HttpContextBase.

http://www.hanselman.com/blog/ASPNETMVCSessionAtMix08TDDAndMvcMockHelpers.aspx

Gerardo Contijoch
źródło
Super, to mi pomogło, chociaż musiałem dodać trochę dodatkowego kodu do metody FakeHttpContext, aby zatrzymać wysadzanie pomocnika: context.Setup (ctx => ctx.Request.ApplicationPath) .Returns ("/ AntiBlowup"); Ponownie zrefaktorowałem kod, więc używa nowszej składni Setup (). Dzięki.
RichardOD
2

Opierając się na odpowiedzi @ eu-ge-ne, która bardzo mi pomogła:

Miałem ActionResult, który wykonał przekierowanie, a także wywołanie UpdateModel z parametrem FormCollection. Aby UpdateModel () działał, musiałem dodać to do mojego Mocked HttpRequestBase:

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

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

Aby sprawdzić, czy przekierowany adres URL był poprawny, możesz wykonać następujące czynności:

RedirectResult result = controller.ActionName(modelToSubmit, collection) as RedirectResult;
Assert.AreEqual("/Expected/URL", result.Url);
jebcrum
źródło