Как смоделировать путь приложения при модульном тестировании веб-приложения - PullRequest
11 голосов
/ 21 июня 2011

Я тестирую код в HTML-помощнике MVC, который выдает ошибку при попытке получить путь к приложению:

//appropriate code that uses System.IO.Path to get directory that results in:
string path = "~\\Views\\directory\\subdirectory\\fileName.cshtml";
htmlHelper.Partial(path, model, viewData); //exception thrown here

Выдается исключение

Система.Web.HttpException: относительный виртуальный путь приложения '~ / Views / directory / subdirectory / fileName.cshtml' нельзя сделать абсолютным, поскольку путь к приложению неизвестен.

Следуя советам Как решить проблему с путем к изображению при тестировании HtmlHelper?
Я подделал (используя Moq):

  • Request.Url, чтобы вернуть строку
  • Request.RawUrl для возврата строки
  • Request.ApplicationPath для возврата строки
  • Request.ServerVariables для возврата нуля NameValueCollection
  • Response.ApplyAppPathModifier(string virtualPath) для возврата строки

Что еще нужно, чтобы этот код мог запускаться в контексте модульного теста?
или
Какой еще подход я должен использовать для динамического отображения частичного представлениявстроенная строка?

Ответы [ 5 ]

9 голосов
/ 30 июня 2011

В качестве альтернативы насмешливым встроенным классам .net вы можете

public interface IPathProvider
{
    string GetAbsolutePath(string path);
}

public class PathProvider : IPathProvider
{
    private readonly HttpServerUtilityBase _server;

    public PathProvider(HttpServerUtilityBase server)
    {
        _server = server;
    }

    public string GetAbsolutePath(string path)
    {
        return _server.MapPath(path);
    }
}

Используйте приведенный выше класс для получения абсолютных путей.

И для Для модульного тестирования вы можете смоделировать и внедрить реализацию IPathProvider, которая будет работать в среде модульного тестирования.

- ОБНОВЛЕННЫЙ КОД

3 голосов
/ 10 мая 2016

Я включаю решение из сообщения в блоге, которое больше недоступно (http://blog.jardalu.com/2013/4/23/httprequest_mappath_vs_httpserverutility_mappath)

Полный код: http://pastebin.com/ar05Ze7p

Ратна (*Код 1009 * использует "HttpServerUtility.MapPath" для сопоставления виртуальных путей с физическим путем к файлу. Этот конкретный код очень хорошо работал для продукта. В нашей последней итерации мы заменяем HttpServerUtility.MapPath на HttpRequest.MapPath.

Под капотами HttpServerUtility.MapPath и HttpRequest.MapPath представляют собой один и тот же код и приведут к одному и тому же отображению. Оба эти метода проблематичны, когда речь идет о модульном тестировании.

Поиск "server.mappath null«Ссылка» в вашей любимой поисковой системе. Вы получите более 10 000 обращений. Почти все эти обращения вызваны тем, что тестовый код вызывает HttpContext.Current и HttpServerUtility.MapPath. Когда код ASP.NET выполняется без HTTP, HttpContext.Current будетбыть нулевым.

Эта проблема (HttpContext.Current is null) может быть очень легко решена с помощью crесть HttpWorkerRequest и инициализировать HttpContext.Current с этим.Вот код для этого -

string appPhysicalDir = @"c:\inetpub\wwwroot";
string appVirtualDir = "/";

SimpleWorkerRequest request = new SimpleWorkerRequest(appVirtualDir, appPhysicalDir, "/", null, new StringWriter());
HttpContext.Current = new HttpContext(request);

С помощью этого простого кода в модульном тесте HttpContext.Current инициализируется.Фактически, если вы заметили, HttpContext.Current.Server (HttpServerUtility) также будет отключен.Однако в тот момент, когда код пытается использовать Server.MapPath, будет выдано следующее исключение.

System.ArgumentNullException occurred
  HResult=-2147467261
  Message=Value cannot be null.
Parameter name: path
  Source=mscorlib
  ParamName=path
  StackTrace:
       at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional)
  InnerException: 
            HttpContext.Current = context;

Фактически, если код использует HttpContext.Current.Request.MapPath, он получиттакое же исключение.Если в коде используется Request.MapPath, проблема может быть легко решена в модульном тесте.Следующий код в модульном тестировании показывает, как.

string appPhysicalDir = @"c:\inetpub\wwwroot";
string appVirtualDir = "/";

SimpleWorkerRequest request = new SimpleWorkerRequest(appVirtualDir, appPhysicalDir, "/", null, new StringWriter());
FieldInfo fInfo = request.GetType().GetField("_hasRuntimeInfo", BindingFlags.Instance | BindingFlags.NonPublic);
fInfo.SetValue(request, true);
HttpContext.Current = new HttpContext(request);

В приведенном выше коде работающий с запросом сможет определить путь к карте.Однако этого недостаточно, потому что HttpRequest не имеет установленного HostingEnvironment (который разрешает MapPath).К сожалению, создание HostingEnvironment не является тривиальным.Таким образом, для модульного тестирования создается «фиктивный хост», который просто обеспечивает функциональность MapPath.Опять же, этот MockHost взламывает много внутреннего кода.Вот псевдокод для фиктивного хоста.Полный код можно скачать здесь: http://pastebin.com/ar05Ze7p

public MockHost(physicalDirectory, virtualDirectory){ ... }
public void Setup()
{
   Create new HostingEnvironment
   Set Call Context , mapping all sub directories as virtual directory
   Initialize HttpRuntime's HostingEnvironment with the created one
}

С помощью приведенного выше кода, когда MapPath вызывается в HttpRequest, он должен иметь возможность разрешить путь.

В качестве последнего шага,в модульном тесте добавьте следующий код -

MockHost host = new MockHost(@"c:\inetpub\wwwroot\", "/");
host.Setup();

Так как теперь HostingEnvironment инициализирован, тестовый код сможет разрешать виртуальные пути при вызове метода HttpContext.Current.Request.MapPath (вместе сс HostingEnvironment.MapPath и HttpServerUtility.MapPath).

Загрузите код MockHost здесь: http://pastebin.com/ar05Ze7p

3 голосов
/ 30 апреля 2014

Для чего бы это ни стоило, я столкнулся с той же ошибкой и проследил за ней через источник System.Web, чтобы найти, что это происходит, потому что HttpRuntime.AppDomainAppVirtualPathObject равно нулю.

Это неизменяемое свойство в синглтоне HttpRuntime, инициализируется следующим образом:

Thread.GetDomain().GetData(key) as String

, где ключ ".appVPath".т.е. это происходит из AppDomain.Может быть возможно подделать это с помощью:

Thread.GetDomain().SetData(key, myAbsolutePath)

Но, честно говоря, подход в принятом ответе звучит намного лучше, чем перебирать AppDomain.

1 голос
/ 08 апреля 2015

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

Вместо этого просто используйте ApplicationHost.CreateApplicationHost для создания правильно инициализированной AppDomain.Затем запустите свой тестовый код из этого домена, используя AppDomain.DoCallback.

using System;
using System.Web.Hosting;

public class AppDomainUnveiler : MarshalByRefObject
{
    public AppDomain GetAppDomain()
    {
        return AppDomain.CurrentDomain;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var appDomain = ((AppDomainUnveiler)ApplicationHost.CreateApplicationHost(
            typeof(AppDomainUnveiler),
            "/",
            Path.GetFullPath("../Path/To/WebAppRoot"))).GetAppDomain();
        try
        {
            appDomain.DoCallback(TestHarness);
        }
        finally
        {
            AppDomain.Unload(appDomain);
        }
    }

    static void TestHarness()
    {
        //…
    }
}

Примечание. При попытке выполнить сам мой код запуска тестов находился в отдельной сборке от WebAppRoot/binкаталог.Это проблема, потому что, когда HostApplication.CreateApplicationHost создает новый AppDomain, он устанавливает в своем базовом каталоге что-то вроде вашего WebAppRoot каталога.Следовательно, вы должны определить AppDomainUnveiler в сборке, которую можно обнаружить в каталоге WebAppRoot/bin (поэтому она должна находиться в базе кода вашего веб-приложения и, к сожалению, не может храниться отдельно в тестовой сборке).Я полагаю, что если вы хотите сохранить свой тестовый код в отдельной сборке, вы подписываетесь на AppDomain.AssemblyResolve в конструкторе AppDomainUnveiler.Как только ваша тестовая сборка получает объект AppDomain, она может использовать AppDomain.SetData для передачи информации о том, где загрузить тестовую сборку.Тогда ваш AssemblyResolve подписчик может использовать AppDomain.GetData, чтобы узнать, откуда загрузить тестовую сборку.(Я не уверен, но вид объектов, которые вы можете SetData / GetData, может быть весьма ограничен - я просто использовал string для безопасности).Это немного раздражает, но я думаю, что это лучший способ отделить проблемы в этой ситуации.

0 голосов
/ 22 сентября 2014
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
        var moqRequestContext = new Mock<RequestContext>(MockBehavior.Strict);
        request.SetupGet<RequestContext>(r => r.RequestContext).Returns(moqRequestContext.Object);
        var routeData = new RouteData();
        routeData.Values.Add("key1", "value1");
        moqRequestContext.Setup(r => r.RouteData).Returns(routeData);

        request.SetupGet(x => x.ApplicationPath).Returns(PathProvider.GetAbsolutePath(""));

public interface IPathProvider
{
    string GetAbsolutePath(string path);
}

public class PathProvider : IPathProvider
{
     private readonly HttpServerUtilityBase _server;

     public PathProvider(HttpServerUtilityBase server)
     {
        _server = server;
     }

    public string GetAbsolutePath(string path)
    {
        return _server.MapPath(path);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...