Тестирование WebApi Controller Url.Link

У меня есть следующее действие контроллера

public void Post(Dto model)
{
    using (var message = new MailMessage())
    {
        var link = Url.Link("ConfirmAccount", new { model.Id });

        message.To.Add(model.ToAddress);
        message.IsBodyHtml = true;
        message.Body = string.Format(@"<p>Click <a href=""{0}"">here</a> to complete your registration.<p><p>You may also copy and paste this link into your browser.</p><p>{0}</p>", link);

        MailClient.Send(message);
    }
}

Чтобы проверить это, мне нужно настроить контекст контроллера

var httpConfiguration = new HttpConfiguration(new HttpRouteCollection { { "ConfirmAccount", new HttpRoute() } });
var httpRouteData = new HttpRouteData(httpConfiguration.Routes.First());
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost");
sut = new TheController
{
    ControllerContext = new HttpControllerContext(httpConfiguration, httpRouteData, httpRequestMessage),
    MailClient = new SmtpClient { PickupDirectoryLocation = location }
};

Это похоже на множество настроек для проверки создания ссылки. Есть ли более чистый способ сделать это? Я читал о серверах на памяти, но похоже, что он больше подходит для httpclient, чем непосредственно для тестирования контроллера.

Ответ 1

Ниже приведен абсолютный минимальный код, необходимый для тестирования UrlHelper без какой-либо насмешливой библиотеки. То, что меня бросило (и мне потребовалось некоторое время, чтобы выследить), было то, что вам нужно установить IHttpRouteData запроса. Если вы не используете экземпляр IHttpRoute, вы не сможете создать виртуальный путь, в результате получивший пустой URL.

    public class FooController : ApiController
    {
        public string Get()
        {
            return Url.Link(RouteNames.DefaultRoute, new { controller = "foo", id = "10" });
        }
    }

    [TestFixture]
    public class FooControllerTests
    {
        FooController controller;

        [SetUp]
        public void SetUp()
        {
            var config = new HttpConfiguration();

            config.Routes.MapHttpRoute(
                name: "Default",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional });

            var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost");
            request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
            request.Properties[HttpPropertyKeys.HttpRouteDataKey] = new HttpRouteData(new HttpRoute());

            controller = new FooController
            {
                Request = request
            };
        }

        [Test]
        public void Get_returns_link()
        {
            Assert.That(controller.Get(), Is.EqualTo("http://localhost/api/foo/10"));
        }
    }

Ответ 2

Я начал использовать этот подход с помощью Web API 2.0.

Если вы используете насмешливую библиотеку (и вы действительно должны это делать для реальных тестов в реальном мире), вы можете напрямую издеваться над объектом UrlHelper, так как все методы на нем virtual.

var mock = new Mock<UrlHelper>();
mock.Setup(m => m.Link(It.IsAny<string>(), It.IsAny<object>())).Returns("test url");

var controller = new FooController {
    Url = mock.Object
};

Это намного более чистое решение, чем ответ Ben Foster, так как с этим подходом вам нужно добавить маршруты в конфигурацию для каждого имени, которое вы используете. Это может легко измениться или быть смехотворно большим количеством маршрутов для настройки.

Ответ 3

Я сталкиваюсь с тем же идиотизмом. Все ссылки, которые я могу найти, хотят, чтобы вы Mock Request/Controller, который (как вы указали) много работал.

Конкретные ссылки:

Я не собираюсь разбираться в реальных средах Mocking, поэтому у меня есть вспомогательный класс для "сборки" моего контроллера. Поэтому вместо

sut = new TheController { ... }

Я использую что-то вроде:

// actually rolled together to `sut = MyTestSetup.GetController(method, url)`
sut = new TheController()...
MyTestSetup.FakeRequest(sut, HttpMethod.Whatever, "~/the/expected/url");

Для справки, метод в основном:

public void FakeRequest(ApiController controller, HttpMethod method = null, string requestUrl = null, string controllerName = null) {
    HttpConfiguration config = new HttpConfiguration();
    // rebuild the expected request
    var request = new HttpRequestMessage( null == method ? this.requestMethod : method, string.IsNullOrWhiteSpace(requestUrl) ? this.requestUrl : requestUrl);
    //var route = System.Web.Routing.RouteTable.Routes["DefaultApi"];
    var route  = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");

    // TODO: get from application?  maybe like https://stackoverflow.com/a/5943810/1037948
    var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", string.IsNullOrWhiteSpace(controllerName) ? this.requestController : controllerName } });

    controller.ControllerContext = new HttpControllerContext(config, routeData, request);

    // attach fake request
    controller.Request = request;
    controller.Request.Properties[/* "MS_HttpConfiguration" */ HttpPropertyKeys.HttpConfigurationKey] = config;
}