Юнит-тестирование Url.Action (используя Rhino Mocks?) - PullRequest
12 голосов
/ 04 июня 2009

Я пытаюсь написать тест для метода расширения UrlHelper, который используется следующим образом:

Url.Action<TestController>(x => x.TestAction());

Однако я не могу настроить его правильно, чтобы я мог создать новый UrlHelper и затем утверждать, что возвращенный URL-адрес был ожидаемым. Это то, что у меня есть, но я открыт для всего, что также не включает насмешки. ; О)

        [Test]
    public void Should_return_Test_slash_TestAction()
    {
        // Arrange
        RouteTable.Routes.Add("TestRoute", new Route("{controller}/{action}", new MvcRouteHandler()));
        var mocks = new MockRepository();
        var context = mocks.FakeHttpContext(); // the extension from hanselman
        var helper = new UrlHelper(new RequestContext(context, new RouteData()), RouteTable.Routes);

        // Act
        var result = helper.Action<TestController>(x => x.TestAction());

        // Assert
        Assert.That(result, Is.EqualTo("Test/TestAction"));
    }

Я попытался изменить его на urlHelper.Action («Test», «TestAction»), но все равно не получится, поэтому я знаю, что это не мой метод расширения, который не работает. NUnit возвращает:

NUnit.Framework.AssertionException: Expected string length 15 but was 0. Strings differ at index 0.
Expected: "Test/TestAction"
But was:  <string.Empty>

Я проверил, что маршрут зарегистрирован и работает, и я использую расширение Hanselmans для создания поддельного HttpContext. Вот как выглядит мой метод расширения UrlHelper:

        public static string Action<TController>(this UrlHelper urlHelper, Expression<Func<TController, object>> actionExpression) where TController : Controller
    {
        var controllerName = typeof(TController).GetControllerName();
        var actionName = actionExpression.GetActionName();

        return urlHelper.Action(actionName, controllerName);
    }

    public static string GetControllerName(this Type controllerType)
    {
        return controllerType.Name.Replace("Controller", string.Empty);
    }

    public static string GetActionName(this LambdaExpression actionExpression)
    {
        return ((MethodCallExpression)actionExpression.Body).Method.Name;
    }

Любые идеи о том, что мне не хватает, чтобы заставить его работать ??? / Кристоффер

Ответы [ 3 ]

11 голосов
/ 20 июня 2010

Причина, по которой он не работает, заключается в том, что внутренне объект RouteCollection вызывает метод ApplyAppPathModifier для HttpResponseBase. Похоже, что фиктивный код Хансельмана не устанавливает никаких ожиданий для этого метода, поэтому он возвращает null, поэтому все ваши вызовы метода Action в UrlHelper возвращают пустую строку. Исправление будет состоять в том, чтобы настроить ожидание для метода ApplyAppPathModifier макета HttpResponseBase, чтобы просто возвращать переданное ему значение. Я не эксперт по Rhino Mocks, поэтому я не совсем уверен в синтаксисе. Если вы используете Moq, то это будет выглядеть так:

httpResponse.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>()))
    .Returns((string s) => s);

Или, если вы просто используете свернутый вручную макет, что-то вроде этого будет работать:

internal class FakeHttpContext : HttpContextBase
{
    private HttpRequestBase _request;
    private HttpResponseBase _response;

    public FakeHttpContext()
    {
        _request = new FakeHttpRequest();
        _response = new FakeHttpResponse();
    }

    public override HttpRequestBase Request
    {
        get { return _request; }
    }

    public override HttpResponseBase Response
    {
        get { return _response; }
    }
}

internal class FakeHttpResponse : HttpResponseBase
{
    public override string ApplyAppPathModifier(string virtualPath)
    {
        return virtualPath;
    }
}

internal class FakeHttpRequest : HttpRequestBase
{
    private NameValueCollection _serverVariables = new NameValueCollection();

    public override string ApplicationPath
    {
        get { return "/"; }
    }

    public override NameValueCollection ServerVariables
    {
        get { return _serverVariables; }
    }
}

Приведенный выше код должен быть минимально необходимой реализацией HttpContextBase для прохождения модульного теста для UrlHelper. Я попробовал это, и это сработало. Надеюсь, это поможет.

2 голосов
/ 09 июня 2010

Мне удалось протестировать метод BuildUrlFromExpression, но мне нужно было зарегистрировать мой RouteTable.Routes перед запуском тестов:

[ClassInitialize]
public static void FixtureSetUp(TestContext @__testContext)
{
    MvcApplication.RegisterRoutes(RouteTable.Routes);
}

Затем заглушите / настройте эти свойства:

HttpRequestBase request = mocks.PartialMock<HttpRequestBase>();
request.Stub(r => r.ApplicationPath).Return(string.Empty);

HttpResponseBase response = mocks.PartialMock<HttpResponseBase>();
SetupResult.For(response.ApplyAppPathModifier(Arg<String>.Is.Anything)).IgnoreArguments().Do((Func<string, string>)((arg) => { return arg; }));

После этого метод BuildUrlFromExpression вернул uls, как и ожидалось.

1 голос
/ 04 июня 2009

Я знаю, что это не дает прямого ответа на ваш вопрос, но есть ли причина, по которой вы пытаетесь написать свой собственный общий метод расширения, а не тот, который доступен в сборке MVC Futures? (Microsoft.Web.Mvc.dll) Или вы на самом деле пытаетесь провести модульное тестирование метода расширения msft?

[Редактировать 1] Извините, я думал о вспомогательном расширении Html в Futures.

А пока я попробую свои силы на модульном тесте, чтобы увидеть, получу ли я такой же результат.

[Редактировать 2] Хорошо, так что это еще не совсем работает, но это не взрывается. Результат просто возвращает пустую строку. Я взял несколько помощников насмешников по Mvc у Скотта Хансельмана по этой ссылке 1010 *

Я также создал Url.Action<TController> метод вместе с вспомогательными методами, которые я скопировал из источника Mvc:

public static string Action<TController>(this UrlHelper helper, Expression<Action<TController>> action) where TController : Controller
{
    string result = BuildUrlFromExpression<TController>(helper.RequestContext, helper.RouteCollection, action);
    return result;
}

public static string BuildUrlFromExpression<TController>(RequestContext context, RouteCollection routeCollection, Expression<Action<TController>> action) where TController : Controller
{
    RouteValueDictionary routeValuesFromExpression = GetRouteValuesFromExpression<TController>(action);
    VirtualPathData virtualPath = routeCollection.GetVirtualPath(context, routeValuesFromExpression);
    if (virtualPath != null)
    {
        return virtualPath.VirtualPath;
    }
    return null;
}

public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) where TController : Controller
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    MethodCallExpression body = action.Body as MethodCallExpression;
    if (body == null)
    {
        throw new ArgumentException("MvcResources.ExpressionHelper_MustBeMethodCall", "action");
    }
    string name = typeof(TController).Name;
    if (!name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
    {
        throw new ArgumentException("MvcResources.ExpressionHelper_TargetMustEndInController", "action");
    }
    name = name.Substring(0, name.Length - "Controller".Length);
    if (name.Length == 0)
    {
        throw new ArgumentException("MvcResources.ExpressionHelper_CannotRouteToController", "action");
    }
    RouteValueDictionary rvd = new RouteValueDictionary();
    rvd.Add("Controller", name);
    rvd.Add("Action", body.Method.Name);
    AddParameterValuesFromExpressionToDictionary(rvd, body);
    return rvd;
}

private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call)
{
    ParameterInfo[] parameters = call.Method.GetParameters();
    if (parameters.Length > 0)
    {
        for (int i = 0; i < parameters.Length; i++)
        {
            Expression expression = call.Arguments[i];
            object obj2 = null;
            ConstantExpression expression2 = expression as ConstantExpression;
            if (expression2 != null)
            {
                obj2 = expression2.Value;
            }
            else
            {
                Expression<Func<object>> expression3 = Expression.Lambda<Func<object>>(Expression.Convert(expression, typeof(object)), new ParameterExpression[0]);
                obj2 = expression3.Compile()();
            }
            rvd.Add(parameters[i].Name, obj2);
        }
    }
}

И, наконец, вот мой тест:

    [Test]
    public void GenericActionLinkHelperTest()
    {
        RouteRegistrar.RegisterRoutesTo(RouteTable.Routes);

        var mocks = new MockRepository();
        var context = mocks.FakeHttpContext(); // the extension from hanselman

        var helper = new UrlHelper(new RequestContext(context, new RouteData()), RouteTable.Routes);
        string result = helper.Action<ProjectsController>(x => x.Index());

        // currently outputs an empty string, so something is fudded up.
        Console.WriteLine(result);
    }

Пока не уверен, почему вывод - пустая строка, но я буду продолжать возиться с этим, так как у меня будет время. Мне было бы интересно узнать, если вы найдете решение в то же время.

...