Primavera proporciona integración entre MockMvc y HtmlUnit. Esto facilita la realización de pruebas de un extremo a otro cuando se utilizan vistas basadas en HTML. Esta integración le permite:

  • Probar fácilmente páginas HTML utilizando herramientas como HtmlUnit, WebDriver y Geb, sin necesidad de implementarlo en un contenedor de servlets.

  • Pruebe JavaScript dentro de las páginas.

  • Prueba JavaScript dentro de las páginas.

  • Prueba JavaScript dentro de las páginas.

  • Pruebe JavaScript dentro de las páginas.

  • Opcionalmente utilice servicios simulados para acelerar las pruebas.

  • Asegúrese de que las pruebas de un extremo a otro en el contenedor y pruebas de integración fuera de la lógica de uso compartido del contenedor.

MockMvc trabaja en tecnologías de plantillas que son independientes del contenedor de servlets (por ejemplo, Thymeleaf, FreeMarker y otros), pero no funciona con la tecnología JSP porque depende del contenedor de servlets.

¿Por qué se necesita la integración de HtmlUnit?

El La pregunta más obvia que me viene a la mente es: “¿Por qué es necesario?”. La forma más sencilla de encontrar la respuesta es estudiar una aplicación de ejemplo muy clara. Supongamos que tiene una aplicación web Spring MVC que admite operaciones CRUD en un objeto Message. La aplicación también admite la paginación de todos los mensajes. ¿Cómo probarlo?

Utilizando Spring MVC Test, podemos comprobar fácilmente si podemos crear un Message, como este:

Java
    MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param("summary", "Spring Rocks")
        .param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
Kotlin

@Test
fun test() {
    mockMvc.post("/messages/") {
        param("summary", "Spring Rocks")
        param("text", "In case you didn't know, Spring Rocks!")
    }.andExpect {
        status().is3xxRedirection()
        redirectedUrl("/messages/123")
    }
}

¿Qué pasa si necesitamos probar el envío de un formulario que nos permite crear un mensaje? Por ejemplo, digamos que nuestro formulario se parece al siguiente fragmento:

        <form id="messageForm" action="/messages/" method="post">
    <div class="pull-right"><a href="/messages/">Messages</a>
    </div>
    <label for="summary">Summary</label>
    <input type="text" class="required" id="summary" name="summary" value="" />
    <label for="text">Message</label>
    <textarea id="text" name="text"></textarea>
    <div class="form-actions">
        <input type="submit" value="Create" />
    </div>
</form>

¿Cómo podemos hacer que nuestro formulario solicite correctamente la creación de un nuevo mensaje? Un intento ingenuo podría verse así:

Java

mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());
Kotlin

mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='summary']") { exists() }
    xpath("//textarea[@name='text']") { exists() }
}

Esta prueba tiene algunas deficiencias obvias. Si actualizamos nuestro controlador para usar el parámetro message en lugar de text, nuestra prueba de formulario pasará incluso si el formulario HTML no está sincronizado con el controlador. Para resolver este problema, podemos combinar nuestras dos pruebas de la siguiente manera:

Java

String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
        .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param(summaryParamName, "Spring Rocks")
        .param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));;
Kotlin

val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='$summaryParamName']") { exists() }
    xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
    param(summaryParamName, "Spring Rocks")
    param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
    status().is3xxRedirection()
    redirectedUrl("/messages/123")
}

Esto reducirá el riesgo de que nuestra prueba pase incorrectamente, pero hay Aún quedan algunos problemas:

  • ¿Qué hacer si hay varios formularios en la página? Por supuesto, podemos actualizar nuestras expresiones XPath, pero se vuelven cada vez más complejas a medida que consideramos más factores: ¿Son los campos del tipo correcto? ¿Están habilitados los campos? Y así sucesivamente.

  • Otro problema es que estamos haciendo el doble de trabajo de lo esperado. Primero debemos validar la vista y luego enviarla con los mismos parámetros que acabamos de validar. Lo ideal es que todo esto se pueda hacer de una vez.

  • Finalmente, todavía hay algunas cosas que no podemos tener en cuenta. Por ejemplo, ¿qué pasa si el formulario tiene validación de JavaScript que también queremos probar?

Un problema común es que probar una página web no implica una interacción única. Más bien, es una combinación de cómo un usuario interactúa con una página web y cómo esa página web interactúa con otros recursos. Por ejemplo, el resultado del envío de un formulario se utiliza como entrada para que el usuario cree un mensaje. Además, el envío de nuestro formulario puede utilizar potencialmente recursos adicionales que afectan la lógica de la página, como la validación de JavaScript.

¿Las pruebas de integración pueden ayudar?

Para resolver los problemas mencionados anteriormente, Podemos realizar pruebas de integración de un extremo a otro, pero tiene varias desventajas. Veamos cómo probar una vista que nos permita ver mensajes. Es posible que necesitemos probar lo siguiente:

  • ¿Nuestra página muestra una notificación al usuario de que no hay resultados si las publicaciones están vacías?

  • ¿Nuestra página muestra correctamente un solo mensaje?

  • ¿Nuestra página admite correctamente la paginación?

Para Para configurar estas pruebas, debemos asegurarnos de que nuestra base de datos contenga los mensajes necesarios. Esto introduce una serie de problemas adicionales:

  • Asegurar que los mensajes correctos estén en la base de datos puede ser un proceso que requiere mucho tiempo. (Dadas las restricciones de clave externa).

  • Las pruebas pueden ser lentas porque cada prueba debe garantizar que la base de datos esté en el estado correcto.

  • Debido a que nuestra base de datos debe estar en un estado determinado, no podemos ejecutar pruebas en paralelo.

  • Ejecutar aserciones sobre elementos como ID generados automáticamente, marcas de tiempo, etc. puede ser difícil.

Estos problemas no significan que las pruebas de integración de un extremo a otro deban abandonarse por completo. En su lugar, podemos reducir la cantidad de pruebas de integración de un extremo a otro refactorizando nuestras pruebas detalladas para utilizar servicios simulados que sean mucho más rápidos, más confiables y sin efectos secundarios. Luego puede ejecutar una pequeña cantidad de pruebas de integración verdaderas de un extremo a otro que prueban flujos de trabajo simples para asegurarse de que todo funcione como se espera.

Presentación de la integración HtmlUnit

¿Cómo logramos una ¿Equilibrio entre probar cómo funcionan nuestras páginas y mantener un rendimiento adecuado en nuestro conjunto de pruebas? Respuesta: "Al integrar MockMvc con HtmlUnit".

Opciones de integración de HtmlUnit

Si necesita integrar MockMvc con HtmlUnit, hay varias opciones:

  • MockMvc y HtmlUnit: utilice esta opción si desea utilizar bibliotecas HtmlUnit sin formato.

  • MockMvc y WebDriver: utilice esta opción para facilitar el desarrollo y la reutilización del código entre la integración y el proceso de extremo a extremo. finalizar las fases de prueba.

  • MockMvc y Geb: utilice esta opción si necesita usar Groovy para realizar pruebas, facilitar el desarrollo y reutilizar el código entre las fases de integración y prueba de un extremo a otro.

MockMvc y HtmlUnit

Esta sección describe cómo integrar MockMvc y HtmlUnit. Utilice esta opción si desea utilizar las bibliotecas HtmlUnit sin formato.

Configuración de MockMvc y HtmlUnit

Primero, asegúrese de haber activado la dependencia de prueba en net.sourceforge.htmlunit:htmlunit. Para usar HtmlUnit con Apache HttpComponents 4.5+, debe usar HtmlUnit 2.18 o superior.

Puede crear fácilmente un WebClient desde HtmlUnit que se integrará con MockMvc usando MockMvcWebClientBuilder como se muestra a continuación:

Java

WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Este es un ejemplo sencillo del uso de MockMvcWebClientBuilder. Para uso avanzado, consulte "Uso avanzado deMockMvcWebClientBuilder".

De esta manera podemos garantizar que cualquier URL que haga referencia a localhost como servidor será dirigida a nuestra instancia MockMvc sin la necesidad de una conexión HTTP real. La solicitud de cualquier otra URL se realiza a través de la conexión de red como es habitual. Esto nos permite probar fácilmente el uso de redes de entrega (y distribución) de contenido (CDN).

Uso de MockMvc y HtmlUnit

Ahora podemos usar HtmlUnit como estándar, pero sin necesidad de desplegar nuestra aplicación en un contenedor de servlets. Por ejemplo, podría pedirle a la vista que cree un mensaje como este:

Java
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
Kotlin
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
La ruta de contexto predeterminada es "" . También puede especificar la ruta al contexto, como se describe en "Uso avanzado de MockMvcWebClientBuilder".

Una vez que tengamos un enlace a HtmlPage, podemos completar inmediatamente el formulario y enviarlo para crear un mensaje, como se muestra en el siguiente ejemplo:

Java
        
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
        
Kotlin

val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()

Finalmente, puedes asegúrese de que se haya creado correctamente un nuevo mensaje. Las siguientes afirmaciones utilizan la biblioteca AssertJ:

Java
 a
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
Kotlin
 
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")

El código anterior mejora nuestro pruebe MockMvc de varias maneras. Primero, ya no necesitamos validar explícitamente nuestro formulario y luego crear una solicitud que parezca un formulario. En su lugar, solicitamos el formulario, lo completamos y lo enviamos, reduciendo así significativamente el retraso en el procesamiento.

Otro factor importante es que HtmlUnit utiliza el motor Mozilla Rhino para calcular la salida de JavaScript. Esto significa que también podemos probar la lógica de JavaScript en nuestras páginas.

Más información sobre el uso de HtmlUnit Consulte la documentación sobre el uso de HtmlUnit.

Uso avanzado de MockMvcWebClientBuilder

Hasta ahora en los ejemplos hemos utilizado MockMvcWebClientBuilder de la forma más sencilla, creando un WebClient basado en un WebApplicationContext, cargado para nosotros por Spring TestContext Framework. Este enfoque se repite en el siguiente ejemplo:

Java

WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}

También podemos especificar opciones de configuración adicionales, como se muestra en el siguiente ejemplo:

Java
        
WebClient webClient;
@BeforeEach
void setup() {
    webClient = MockMvcWebClientBuilder
        // demuestra el uso de MockMvcConfigurer (Spring Security)
        webAppContextSetup(context, springSecurity())
        // solo por ejemplo - predeterminado ""
        .contextPath("")
        // se usa MockMvc por defecto sólo para localhost;
        // la siguiente parte usará MockMvc también para example.com y example.org
        .useMockMvcForHosts("example.com","example.org")
        .build();
}
Kotlin

lateinit var webClient: WebClient
@BeforeEach
fun setup() {
    webClient = MockMvcWebClientBuilder
        // demuestra el uso de MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // solo por ejemplo - predeterminado ""
        .contextPath("")
        // se usa MockMvc por defecto sólo para localhost;
        // la siguiente parte usará MockMvc también para example.com y example.org
        .useMockMvcForHosts("example.com","example.org")
        .build()
}

Como alternativa, puedes hacer exactamente la misma configuración configurando una instancia MockMvc por separado y pasándola al MockMvcWebClientBuilder como se muestra a continuación:

Java
        
MockMvc mockMvc = MockMvcBuilders
    .webAppContextSetup(context)
    .apply(springSecurity())
    .build();
webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // solo por ejemplo - predeterminado ""
        .contextPath("")
        // De forma predeterminada, MockMvc se usa solo para localhost;
        // la siguiente parte usará MockMvc también para example.com y example.org
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// No es posible en Kotlin hasta que se solucione https://youtrack.jetbrains.com/issue/KT-22208

Este es un código más sobrecargado, pero al crear un WebClient usando una instancia MockMvc, tenemos toda la funcionalidad de MockMvc a nuestra disposición. .

Puede encontrar más información sobre cómo crear instancias de MockMvc en "Opciones de configuración".

MockMvc y WebDriver

En las secciones anteriores, aprendimos cómo usar MockMvc en combinación con las API HtmlUnit sin procesar. Esta sección utiliza abstracciones adicionales en Selenium WebDriver, lo que hace que todo sea aún más simple.

¿Por qué WebDriver y MockMvc?

Ya podemos usar HtmlUnit y MockMvc, entonces, ¿por qué necesitamos WebDriver? Selenium WebDriver proporciona una API extremadamente elegante que nos permitirá organizar nuestro código fácilmente. Para mostrar mejor cómo funciona esto, veremos un ejemplo en esta sección.

Aunque WebDriver es parte de Selenium, no requiere Selenium Server para ejecutar sus pruebas.

Digamos que debemos asegurarnos de que el mensaje se crea correctamente. Las pruebas implican encontrar elementos de entrada de formulario HTML, completarlos y ejecutar varias afirmaciones.

Este enfoque da como resultado muchas pruebas separadas porque también necesitamos verificar las condiciones de error. Por ejemplo, queremos que se produzca un error al completar solo una parte del formulario. Si completamos todo el formulario, debería aparecer un mensaje recién creado después.

Si uno de los campos se llamara "resumen", podríamos terminar con algo similar al siguiente, repetido en varios lugares en nuestras pruebas:

Java

HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
Kotlin

val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput .setValueAttribute(summary)

¿Qué sucede si cambiamos id a smmry? Esto nos obligará a actualizar todas nuestras pruebas para tener en cuenta este cambio. Esto viola el principio No repetirse (DRY), por lo que idealmente deberíamos separar este código en un método separado, como se muestra a continuación:

Java

public HtmlPage createMessage( HtmlPage currentPage, String summary, String text) {
    setSummary(currentPage, summary);
    // ...
}
public void setSummary(HtmlPage currentPage, String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
}
Kotlin

fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
    setSummary(currentPage, summary);
    // ...
}
fun setSummary(currentPage:HtmlPage , summary: String) {
    val summaryInput = currentPage.getHtmlElementById("summary")
    summaryInput.setValueAttribute(summary)
}

Esto garantiza que no tendremos que actualizar todas nuestras pruebas si cambiamos la interfaz de usuario.

Incluso podemos ir un paso más allá y poner esta lógica en un Objeto que representa la HtmlPage en la que nos encontramos actualmente, como se muestra en el siguiente ejemplo:

Java

public class CreateMessagePage {
    final HtmlPage currentPage;
    final HtmlTextInput summaryInput;
    final HtmlSubmitInput submit;
    public CreateMessagePage(HtmlPage currentPage) {
        this.currentPage = currentPage;
        this.summaryInput = currentPage.getHtmlElementById("summary");
        this.submit = currentPage.getHtmlElementById("submit");
    }
    public <T> T createMessage(String summary, String text) throws Exception {
        setSummary(summary);
        HtmlPage result = submit.click();
        boolean error = CreateMessagePage.at(result);
        return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
    }
    public void setSummary(String summary) throws Exception {
        summaryInput.setValueAttribute(summary);
    }
    public static boolean at(HtmlPage page) {
        return "Create Message".equals(page.getTitleText());
    }
}
Kotlin

class CreateMessagePage(private val currentPage: HtmlPage) {
        val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")
        val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")
        fun <T> createMessage(summary: String, text: String): T {
            setSummary(summary)
            val result = submit.click()
            val error = at(result)
            return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
        }
        fun setSummary(summary: String) {
            summaryInput.setValueAttribute(summary)
        }
        fun at(page: HtmlPage): Boolean {
            return "Create Message" == page.getTitleText()
        }
    }
}

Este patrón se conocía anteriormente como Patrón de objeto de página). Por supuesto, puede hacer esto con HtmlUnit, pero WebDriver proporciona algunas herramientas que facilitan mucho la implementación de este patrón y las veremos en las siguientes secciones.

Configuración de MockMvc y WebDriver

Para usar Selenium WebDriver con el marco de prueba Spring MVC, asegúrese de que su proyecto contenga una dependencia de prueba en org.seleniumhq.selenium:selenium-htmlunit-driver.

Usted Puede crear fácilmente un Selenium WebDriver que se integre con MockMvc usando MockMvcHtmlUnitDriverBuilder, como se muestra en el siguiente ejemplo:

Java

WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}
Este es un ejemplo sencillo del uso de MockMvcHtmlUnitDriverBuilder. Para uso avanzado, consulte " Uso avanzado de MockMvcHtmlUnitDriverBuilder".

En el ejemplo anterior, se garantiza que cualquier URL que haga referencia a localhost como servidor será dirigida a nuestra instancia MockMvc sin la necesidad de una conexión HTTP real. . La solicitud de cualquier otra URL se realiza a través de la conexión de red como es habitual. Esto nos permite probar fácilmente el uso de redes de entrega (y distribución) de contenidos (CDN).

Usando MockMvc y WebDriver

Ahora podemos usar WebDriver como estándar, pero sin necesidad para implementar nuestra aplicación en un contenedor de servlets. Por ejemplo, podría pedirle a la vista que cree un mensaje como este:

Java
CreateMessagePage page = CreateMessagePage.to(driver);
Kotlin
val page = CreateMessagePage.to(driver)

Luego puede completar el formulario y enviarlo para crear un mensaje, de la siguiente manera:

Java

ViewMessagePage viewMessagePage =
    page.createMessage(ViewMessagePage.class , resumen esperado, texto esperado);
Kotlin

val viewMessagePage =
    page.createMessage(ViewMessagePage::class, expectedSummary , texto esperado)

Esto mejorará la estructura de nuestro prueba HtmlUnit utilizando el patrón de objeto de página. Como ya se mencionó en la subsección "¿Por qué WebDriver y MockMvc? "Podemos usar el patrón de objetos de página con HtmlUnit, pero con WebDriver es aún más fácil. Considere la siguiente implementación de CreateMessagePage:

Java
public class CreateMessagePage
        extends AbstractPage { 
    
    private WebElement summary;
    private WebElement text;
    
    @FindBy(css = "input[type=submit]")
    private WebElement submit;
    public CreateMessagePage(WebDriver driver) {
        super(driver);
    }
    public <T> T createMessage(Class<T> resultPage, String summary, String details) {
        this.summary.sendKeys(summary);
        this.text.sendKeys(details);
        this.submit.click();
        return PageFactory.initElements(driver, resultPage);
    }
    public static CreateMessagePage to(WebDriver driver) {
        driver.get("http://localhost:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}
 
  1. CreateMessagePage extiende AbstractPage . No entraremos en detalles sobre AbstractPage, pero en resumen, contiene una funcionalidad común para todas nuestras páginas. Por ejemplo, si nuestra aplicación tiene una barra de navegación, mensajes de error globales y otras funciones, podemos colocar esta lógica en un lugar común.
  2. Tenemos una variable miembro para cada parte de la página HTML que están interesadas en . Son de tipo WebElement. PageFactory de WebDriver nos permite eliminar una gran cantidad de código innecesario desde HtmlUnit: versión de CreateMessagePage, resolviendo automáticamente cada WebElement. Método PageFactory#initElements(WebDriver,Class<T>) resuelve automáticamente cada WebElement utilizando el nombre del campo y buscando por el elemento id o name en la página HTML.
  3. Puedes usar @FindBy anotación para anular la lógica de búsqueda predeterminada. Nuestro ejemplo muestra cómo utilizar la anotación @FindBy para encontrar nuestro botón de envío usando el selector css(input[type=submit]).
Kotlin

class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { 
    
    private lateinit var summary: WebElement
    private lateinit var text: WebElement
    
    @FindBy(css = "input[type=submit]")
    private lateinit var submit: WebElement
    fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
        this.summary.sendKeys(summary)
        text.sendKeys(details)
        submit.click()
        return PageFactory.initElements(driver, resultPage)
    }
    companion object {
        fun to(driver: WebDriver): CreateMessagePage {
            driver.get("http://localhost:9990/mail/messages/form")
            return PageFactory.initElements(driver, CreateMessagePage::class.java)
        }
    }
}
  1. CreateMessagePage extiende AbstractPage. No entraremos en detalles sobre AbstractPage, pero en resumen, contiene una funcionalidad común para todas nuestras páginas. Por ejemplo, si nuestra aplicación tiene una barra de navegación, mensajes de error globales y otras funciones, podemos colocar esta lógica en un lugar común.
  2. Tenemos una variable miembro para cada parte de la página HTML que están interesadas en. Son de tipo WebElement. PageFactory de WebDriver nos permite eliminar una gran cantidad de código innecesario desde HtmlUnit: versión de CreateMessagePage, resolviendo automáticamente cada WebElement. Método PageFactory#initElements(WebDriver,Class<T>) resuelve automáticamente cada WebElement utilizando el nombre del campo y buscando por el elemento id o name en la página HTML.
  3. Puedes usar @FindBy anotación para anular la lógica de búsqueda predeterminada. Nuestro ejemplo muestra cómo utilizar la anotación @FindBy para encontrar nuestro botón de envío usando el selector css(input[type=submit]).

Finalmente, puede verificar que el nuevo mensaje se creó correctamente. Las siguientes aserciones utilizan la biblioteca de aserciones AssertJ:

Java

assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
afirmarThat(viewMessagePage.getSuccess()).isEqualTo("Se creó correctamente un nuevo mensaje");
Kotlin

assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
afirmarThat(viewMessagePage.success).isEqualTo("Se creó correctamente un nuevo mensaje")

Vemos que nuestra página ViewMessagePage nos permite interactuar con nuestro modelo de dominio personalizado. Por ejemplo, abre un método que devuelve un objeto Message:

Java

public Message getMessage() throws ParseException {
    Message message = new Message ();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}
Kotlin
fun getMessage() = Message(getId(), getCreated(), getSummary() , getText())

Luego podemos usar objetos de dominio con todas las funciones en nuestras declaraciones.

Por último, no lo olvide para cerrar de instancia WebDriver cuando se complete la prueba, como se muestra a continuación:

Java

@AfterEach
void destroy() {
    if (driver != null) {
        driver .close();
    }
}
Kotlin

@AfterEach
fun destroy() {
    if (driver != null) {
        driver. close()
    }
}

Para obtener más información sobre el uso de WebDriver, consulte la documentación de Selenium WebDriver.

Uso avanzado de MockMvcHtmlUnitDriverBuilder

Hasta ahora en los ejemplos Usamos MockMvcHtmlUnitDriverBuilder de la forma más sencilla posible, creando un WebDriver basado en el WebApplicationContext cargado para nosotros por Spring TestContext Framework. Este enfoque se repite aquí de la siguiente manera:

Java

WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}

También podemos especificar opciones de configuración adicionales como se muestra a continuación:

Java

WebDriver driver;
@BeforeEach
void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demuestra el uso de MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // solo por ejemplo - predeterminado ""
            .contextPath("")
            // Se usa MockMvc por defecto sólo para localhost;
            // la siguiente parte usará MockMvc también para example.com y example.org
            .useMockMvcForHosts("example.com","example.org")
            .build();
}
Kotlin

lateinit var driver: WebDriver
@BeforeEach
fun setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demuestra usando MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // solo por ejemplo - predeterminado ""
            .contextPath("")
            // De forma predeterminada, MockMvc se usa solo para localhost;
            // la siguiente parte usará MockMvc también para example.com y example.org
            .useMockMvcForHosts("example.com","example.org")
            .build()
}

Como alternativa, puedes hacer exactamente la misma configuración configurando la instancia MockMvc por separado y pasándola al MockMvcHtmlUnitDriverBuilder de esta manera:

Java

MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();
driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // solo por ejemplo - predeterminado ""
        .contextPath("")
        // De forma predeterminada, MockMvc se usa solo para localhost;
        // la siguiente parte usará MockMvc también para example.com y example.org
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// No es posible en Kotlin hasta que se solucione https://youtrack.jetbrains.com/issue/KT-22208

Este es un código más sobrecargado, pero al crear un WebDriver usando una instancia MockMvc, tenemos toda la funcionalidad de MockMvc a nuestra disposición. .

Puede encontrar más información sobre cómo crear instancias de MockMvc en "Opciones de configuración".

MockMvc y Geb

En la sección anterior, vimos cómo usar MockMvc con WebDriver. En esta sección, utilizamos Geb para hacer que nuestras pruebas estén aún más centradas en Groovy.

¿Por qué Geb y MockMvc?

Geb es compatible con WebDriver, por lo que proporciona muchos de los mismos beneficios que recibimos de WebDriver. Sin embargo, Geb facilita aún más las cosas ejecutando el código repetitivo por nosotros.

Configuración de MockMvc y Geb

Puedes inicializar fácilmente el Browser desde Geb con Selenium WebDriver. , que usa MockMvc, de la siguiente manera:


def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}
            
Este es un ejemplo simple del uso de MockMvcHtmlUnitDriverBuilder. Para uso avanzado, consulte " Uso avanzado de MockMvcHtmlUnitDriverBuilder".

De esta manera podemos garantizar que cualquier URL que haga referencia a localhost como servidor será dirigida a nuestra instancia MockMvc sin la necesidad de una conexión HTTP real. La solicitud de cualquier otra URL se realiza a través de la conexión de red como es habitual. Esto nos permite probar fácilmente el uso de redes de entrega (y distribución) de contenido (CDN).

Usando MockMvc y Geb

Ahora podemos usar Geb como estándar, pero sin necesidad para implementar nuestra aplicación en un contenedor de servlets. Por ejemplo, podría pedirle a la vista que cree un mensaje como este:

to CreateMessagePage

Luego puede completar el formulario y enviarlo para crear el mensaje de la siguiente manera:


cuando:
form.summary = expectedSummary
form.text = expectedMessage
submit.click( ViewMessagePage)

Cualquier llamada a método, llamada a propiedad o referencia no reconocida que no se haya encontrado se pasa al objeto de la página actual. Esto elimina una gran cantidad de código repetitivo necesario cuando se utiliza WebDriver directamente.

Al igual que con el uso directo de WebDriver, así es como mejoramos la estructura de nuestro prueba HtmlUnit utilizando el patrón de objeto de página. Como se mencionó anteriormente, puedes usar el patrón de objetos de página con HtmlUnit y WebDriver, pero con Geb todo se vuelve aún más complicado. Echemos un vistazo a nuestra nueva implementación CreateMessagePage basada en Groovy:

class CreateMessagePage extends Page {
    static url = 'messages/form'
    static at = { assert title == 'Messages : Create'; true}
    static content = {
        submit { $('input[type=submit]') }
        form { $('form') }
        errors(required:false) { $('label.error, .alert-error')?.text() }
    }
}

Nuestra CreateMessagePage extiende AbstractPage. No entraremos en detalles sobre Page, pero en resumen, contiene una funcionalidad común para todas nuestras páginas. Definimos la URL donde se puede encontrar esta página. Esto nos permite navegar a la página como se muestra a continuación:

a CreateMessagePage

También tenemos un cierre at que determina si estamos en la página especificada. Debería devolver true si estamos en la página correcta. Es por eso que podemos confirmar que estamos en la página correcta de la siguiente manera:


entonces:
en CreateMessagePage
errores.contains('Este campo es obligatorio.')
Usamos una aserción en el cierre para poder determinar dónde salieron mal las cosas si terminamos en la página equivocada.

A continuación, creamos un cierre content que especifica todos los lugares de interés de la página. Puede utilizar la API de navegador similar a jQuery para seleccionar el contenido que nos interesa.

Finalmente, podemos verificar que el nuevo mensaje se creó exitosamente, de la siguiente manera:

entonces:
en Ver página de mensajes
Success == 'Se creó exitosamente un nuevo mensaje'
identificación
fecha
resumen == resumen esperado
mensaje == mensaje esperado

Puede encontrar más información sobre cómo aprovechar Geb al máximo en el manual de usuario titulado "The Libro de Geb".