Как проверить представления с использованием синтаксиса бритвы в MVC3? - PullRequest
3 голосов
/ 04 января 2012

Я пишу код для тестирования приложения C # MVC3. Я могу проверить контроллеры, но как мне проверить код в представлениях? Сюда входит код в стиле javascript и бритва.

Существуют ли какие-либо инструменты, которые могут макетировать представления или тестировать представления и JavaScript в C # в них?

Ответы [ 2 ]

2 голосов
/ 04 января 2012

Ниже приведено тестирование визуализированного вывода представления.Этот текстовый вывод может, например, быть загружен в DOM для дальнейшего анализа с XPath (используя XmlReader для XHTML или HtmlAgilityPack для HTML в стиле SGML).С некоторыми хорошими вспомогательными методами это позволяет легко проверять определенные части представления, такие как тестирование //a[@href='#'] или что-либо еще, что вы хотите протестировать.Это помогает сделать модульные тесты более стабильными.

Можно было бы ожидать, что это легко при использовании Razor вместо «взорванного» движка WebForms, но, как оказалось, из-за многих внутреннихработы механизма представления Razor и представления с использованием частей (особенно HtmlHelper) жизненного цикла HTTP-запроса.Фактически, для правильного тестирования сгенерированного вывода требуется много работающего кода для получения надежных и подходящих результатов, особенно если вы используете в соединении экзотические вещи, такие как переносимые области (из проекта MVCContrib) и тому подобное.

HTML-помощники для действий и URL-адресов требуют, чтобы маршрутизация была правильно инициализирована, словарь маршрутов был правильно настроен, контроллер также должен существовать, и есть другие "ошибки", связанные с загрузкой данных для представления, такие как настройкасловарь представления данных ...

Мы закончили тем, что создали класс ViewRenderer, который фактически будет создавать экземпляр хоста приложения на физическом пути вашей сети для тестирования (может быть статически кэширован, повторная инициализация для каждогоодиночный тест нецелесообразен из-за задержки инициализации):

host = (ApplicationHost)System.Web.Hosting.ApplicationHost.CreateApplicationHost(typeof(ApplicationHost), "/", physicalDir.FullName);

Класс ApplicationHost в свою очередь наследуется от MarshalByRefObject, поскольку хост будет загружен в отдельный домен приложения.Хост выполняет все виды нечестивой инициализации, чтобы правильно инициализировать HttpApplication (код в global.asax.cs, который регистрирует маршруты и т. Д.) При отключении некоторых аспектов (таких как аутентификация и авторизация). Будьте осторожны, впереди серьезный взлом.Используйте на свой страх и риск.

public ApplicationHost() {
    ApplicationMode.UnitTesting = true; // set a flag on a global helper class to indicate what mode we're running in; this flag can be evaluated in the global.asax.cs code to skip code which shall not run when unit testing
    // first we need to tweak the configuration to successfully perform requests and initialization later
    AuthenticationSection authenticationSection = (AuthenticationSection)WebConfigurationManager.GetSection("system.web/authentication");
    ClearReadOnly(authenticationSection);
    authenticationSection.Mode = AuthenticationMode.None;
    AuthorizationSection authorizationSection = (AuthorizationSection)WebConfigurationManager.GetSection("system.web/authorization");
    ClearReadOnly(authorizationSection);
    AuthorizationRuleCollection authorizationRules = authorizationSection.Rules;
    ClearReadOnly(authorizationRules);
    authorizationRules.Clear();
    AuthorizationRule rule = new AuthorizationRule(AuthorizationRuleAction.Allow);
    rule.Users.Add("*");
    authorizationRules.Add(rule);
    // now we execute a bogus request to fully initialize the application
    ApplicationCatcher catcher = new ApplicationCatcher();
    HttpRuntime.ProcessRequest(new SimpleWorkerRequest("/404.axd", "", catcher));
    if (catcher.ApplicationInstance == null) {
        throw new InvalidOperationException("Initialization failed, could not get application type");
    }
    applicationType = catcher.ApplicationInstance.GetType().BaseType;
}

Метод ClearReadOnly использует отражение, чтобы сделать изменяемую веб-конфигурацию в памяти:

private static void ClearReadOnly(ConfigurationElement element) {
    for (Type type = element.GetType(); type != null; type = type.BaseType) {
        foreach (FieldInfo field in type.GetFields(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.DeclaredOnly).Where(f => typeof(bool).IsAssignableFrom(f.FieldType) && f.Name.EndsWith("ReadOnly", StringComparison.OrdinalIgnoreCase))) {
            field.SetValue(element, false);
        }
    }
}

ApplicationCatcher является"null" TextWriter, в котором хранится экземпляр приложения.Я не мог найти другой способ инициализировать экземпляр приложения и получить его.Суть его довольно проста.

public override void Close() {
    Flush();
}

public override void Flush() {
    if ((applicationInstance == null) && (HttpContext.Current != null)) {
        applicationInstance = HttpContext.Current.ApplicationInstance;
    }
}

Теперь это позволяет нам рендерить практически любое представление (Razor), как если бы оно было размещено на реальном веб-сервере, создавая почти полный жизненный цикл HTTP для его рендеринга.:

private static readonly Regex rxControllerParser = new Regex(@"^(?<areans>.+?)\.Controllers\.(?<controller>[^\.]+)Controller$", RegexOptions.CultureInvariant|RegexOptions.IgnorePatternWhitespace|RegexOptions.ExplicitCapture);

public string RenderViewToString<TController, TModel>(string viewName, bool partial, Dictionary<string, object> viewData, TModel model) where TController: ControllerBase {
    if (viewName == null) {
        throw new ArgumentNullException("viewName");
    }
    using (StringWriter sw = new StringWriter()) {
        SimpleWorkerRequest workerRequest = new SimpleWorkerRequest("/", "", sw);
        HttpContextBase httpContext = new HttpContextWrapper(HttpContext.Current = new HttpContext(workerRequest));
        RouteData routeData = new RouteData();
        Match match = rxControllerParser.Match(typeof(TController).FullName);
        if (!match.Success) {
            throw new InvalidOperationException(string.Format("The controller {0} doesn't follow the common name pattern", typeof(TController).FullName));
        }
        string areaName;
        if (TryResolveAreaNameByNamespace<TController>(match.Groups["areans"].Value, out areaName)) {
            routeData.DataTokens.Add("area", areaName);
        }
        routeData.Values.Add("controller", match.Groups["controller"].Value);
        ControllerContext controllerContext = new ControllerContext(httpContext, routeData, (ControllerBase)FormatterServices.GetUninitializedObject(typeof(TController)));
        ViewEngineResult engineResult = partial ? ViewEngines.Engines.FindPartialView(controllerContext, viewName) : ViewEngines.Engines.FindView(controllerContext, viewName, null);
        if (engineResult.View == null) {
            throw new FileNotFoundException(string.Format("The view '{0}' was not found", viewName));
        }
        ViewDataDictionary<TModel> viewDataDictionary = new ViewDataDictionary<TModel>(model);
        if (viewData != null) {
            foreach (KeyValuePair<string, object> pair in viewData) {
                viewDataDictionary.Add(pair.Key, pair.Value);
            }
        }
        ViewContext viewContext = new ViewContext(controllerContext, engineResult.View, viewDataDictionary, new TempDataDictionary(), sw);
        engineResult.View.Render(viewContext, sw);
        return sw.ToString();
    }
}

Может быть, это поможет вам достичь некоторых результатов.В общем, многие люди говорят, что хлопоты по проверке просмотров не стоят усилий.Я позволю тебе быть судьей этого.

1 голос
/ 04 января 2012

Ознакомьтесь со статьей vantheshark , в которой описано, как смоделировать движок ASP.NET MVC View с помощью NSubstitute .

...