Взаимодействие с формами
В данной книге мы тщательно избегали значимости использования строго типизированных представлений и базирующихся на выражениях вспомогательных HTML-методов. Это позволяло нам пользоваться преимуществами современных инструментов рефакторинга, которые могут обновлять код нашего представления автоматически в случае изменения названий элементов. Почему тогда мы возвращаемся к жестко-закодированным "магическим" строкам в наших тестах пользовательского интерфейса? Мы можем избежать тех же проблем, которые мы решали в рамках использования строго типизированных представлений, применяя в наших тестах пользовательского интерфейса похожие приемы для взаимодействия с формами.
Например, наше представление Edit
уже пользуется преимуществами строго типизированных представлений при отображении страницы редактирования:
Листинг 20-8: Строго типизированное представление, использующее шаблоны редактирования
@using UITesting.Models;
@model ProductForm
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.EditorForModel()
<input type="submit" value="Save" />
}
Строка 1: Объявляет строго типизированное представление
Строка 9: Создает форму
Edit
Наше представление Edit
– это строго типизированное представление для типа модели представления ProductForm
. Мы используем возможности шаблонов редактирования, введенных в ASP.NET MVC 2, для удаления необходимости ручного кодирования индивидуальных элементов input и label. Метод EditorForModel
также позволяет изменять название любого элемента нашей ProductForm
без разрушения нашего представления или действия контроллера.
В нашем тесте пользовательского интерфейса мы можем использовать преимущества строго типизированных представлений посредством использования схожего подхода в рамках вспомогательных методов, базирующихся на выражениях, как это продемонстрировано ниже:
Листинг 20-9: Использование переменного API и синтаксиса, базирующегося на выражениях, для заполнения форм
[Test]
public void Should_update_product_price_successfully()
{
NavigateLink(LocalSiteMap.Nav.Products);
Browser.Link(Find.ByText("Edit")).Click();
ForForm<ProductForm>()
.WithTextBox(form => form.Price, 389.99m)
.Save();
...
}
Строка 6: Использует вспомогательный метод, базирующийся на выражениях
Этот простой переменный интерфейс начинается с указания типа модели представления посредством вызова метода ForForm
. Метод ForForm
создает объект FluentForm
, который мы вкратце рассмотрим. Затем вызов метода WithTextBox
пристыковывается к результату метода ForForm
и принимает в качестве параметра выражение, используемое для определения свойства для ViewModel
, а также значения, которым будет заполняться элемент ввода данных. Наконец, метод Save
нажимает кнопку Save на форме.
Давайте рассмотрим, что происходит за кулисами, начиная с момента вызова метода ForForm
.
Листинг 20-10: МетодForForm
классаWebTestBase
protected FluentForm<TForm> ForForm<TForm>()
{
return new FluentForm<TForm>(Browser);
}
Метод ForForm
принимает единственный родовой параметр, тип формы. Этот метод возвращает объект FluentForm
, в котором упакован набор вспомогательных методов, созданных для взаимодействия со строго типизированным представлением.
Метод ForForm
создает экземпляр нового объекта FluentForm
, передавая объект IE
в конструктор FluentForm
, как это показано ниже.
Листинг 20-11: Класс FluentForm
и конструктор
public class FluentForm<TForm>
{
private readonly IE _browser;
public FluentForm(IE browser)
{
_browser = browser;
}
...
}
Конструктор FluentForm
принимает объект IE
(строка 4) и сохраняет его в закрытом поле (строка 6) для последующих взаимодействий.
Следующий метод, который вызывается в листинге 20-9, – это метод WithTextBox
, продемонстрированный в следующем листинге.
Листинг 20-12: Метод WithTextBox
, базирующийся на выражениях
public FluentForm<TForm> WithTextBox<TField>(
Expression<Func<TForm, TField>> field,
TField value)
{
var name = ExpressionHelper.GetExpressionText(field);
_browser.TextField(Find.ByName(name))
.TypeText(value.ToString());
return this;
}
Наш метод FluentForm
(строки 1-3) содержит другой родовой параметр, TField
, который помогает выполнять проверку значений формы во время компиляции. Первый параметр – это выражение, которое принимает объект типа TForm
и возвращает экземпляр типа TField
. Использование выражения для перехода к элементам типа – это общая практика для осуществления строго типизированной рефлексии. Второй параметр, типа TField
, будет значением, устанавливаемым в элемент ввода данных.
Для корректного размещения элемента ввода данных, базирующегося на данном выражении, мы используем встроенный в ASP.NET MVC класс ExpressionHelper
(строка 5) для того, чтобы сконструировать название элемента пользовательского интерфейса на основании выражения. Что касается нашего первоначального примера, то фрагмент кода form => form.Price
приведет к созданию элемента ввода данных с названием "Price".
Наряду с корректным, сохраняемым при компиляции названием элемента ввода данных, мы используем объект IE
для того, чтобы определить местоположение этого элемента по имени и ввести предоставляемое значение (строки 6-7). Наконец, для обеспечения связывания множественных полей ввода данных, мы возвращаем сам объект FluentForm
.
Преимущества данного подхода – те же самые, что и при использовании строго типизированных представлений и генераторов HTML, базирующихся на выражениях. Мы можем выполнять рефакторинг объектов нашей модели с уверенностью в том, что, несмотря на любые изменения, наши представления будут соответствовать действительности. Благодаря совместному использованию данной методики в наших тестах пользовательского интерфейса, наши тесты больше не будут разрушаться при изменении нашей модели. Если мы удалим элемент из нашей модели представления – например, если он больше не отображается, – наш тест пользовательского интерфейса больше не будет компилироваться. Эту преждевременную реакцию на то, что что-то может измениться, легче обнаружить и урегулировать, нежели ждать провала теста.
После заполнения элемента ввода данных нам необходимо нажать на кнопку Save при помощи нашего метода Save
, как это продемонстрировано ниже:
Листинг 20-13: Метод FluentForm Save
public void Save()
{
_browser.Forms[0].Submit();
}
Несмотря на то, что метод Save
в этом листинге всего лишь публикует первую найденную форму, мы можем использовать множество других методов, если на странице присутствует более одной формы. Таким же образом, как и при определении местоположения ссылок, мы можем добавить контекстную информацию в атрибут class
формы, если это необходимо. В нашем сценарии мы сталкиваемся только с одной формой на страницу, поэтому нам подойдет публикация первой обнаруженной формы.
Теперь, когда наша форма корректно опубликована и находится в работоспособном состоянии, нам необходимо утвердить результаты отправки формы.