Uno de los principales puntos de enfoque de Testing Library es la accesibilidad. Nosotros queremos asegurarnos de que los tests sean fáciles de escribir manteniendo la experiencia del usuario en primer lugar. En lugar de seleccionar elementos DOM a través de sus atributos o clases de identificación, Testing Library utiliza selectores querys que son fáciles de usar.

Traducción en español del artículo original de Tim Deschryver Making sure you're using the correct query publicado el 28 marzo 2022

En las últimas versiones de DOM Testing Library y también de Angular Testing Library (versión 9), se han realizado mejoras en estos selectores. Aparece un selector en particular (get|find|query) ByRole.

Info la documentación:

Se puede usar para buscar cada elemento que se expone en el árbol de accesibilidad usando opción de name puede buscar los elementos devueltos por su nombre accesible.
Esta debería ser nuestra principal forma de buscar para casi todo. No hay nada que no se pueda conseguir con esto. (si no puedes, es posible tu interfaz tiene problemas de accesibilidad). En la mayoría de los casos la utilizaras de la siguiente manera:  getByRole('button', {name: /submit/i}). Si quieres saber más puedes ver la lista de roles.

La respuesta corta a la pregunta "qué selector debo usar" en la mayoría de las veces es el selector ByRole, hay algunos casos en los que este selector no podrá encontrar el elemento, afortunadamente Testing Library proporciona un par de formas de encontrar un selector alternativo o "fallback". Antes de echar un vistazo a cómo a las alternativas, echemos un vistazo a otros beneficios, además de la accesibilidad, que resuelve ByRole.

Soluciones de ByRole

Seleccionar texto roto o con multiple tags

Los selectores *ByText y ByLabelText no pueden encontrar elementos que se dividen en varios elementos. Por ejemplo, dado el siguiente código HTML, es posible consultar el texto "Hello World".

<h3>Hello <span>World</span></h3>

Se puede resolver con la consulta ByRole de la siguiente manera:

// Before 
screen.getByText(/hello world/i)  
// |>Error: TestingLibraryElementError: Unable to find an element with the text: /hello world/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.  
// Using getByRole in version 9 
screen.getByRole('heading',  { name:  /hello world/i  })  
// |> HTMLHeadingElement

Buscando multiple elementos

Hubo momentos en que el selector usamos principalmente fue GetByText  y este retorna más de un elemento. Esto provoca que algunas de nuestros tests fueran frágiles, que es lo que queremos evitar.

<h3>Login now</h3>
<button>Login</button>

Por ejemplo, para encontrar el botón de Login en el HTML anterior, seria de la siguiente forma:

const logins = screen.getAllByText(/login/i) 
const loginButton = logins[1]

Este test fallaría cuando se agregara un nuevo texto de "Login" en el componente. Esto no es bueno, ya que Testing Library tiene como objetivo escribir test mantenibles. Un mejor enfoque para este problema era utilizar la opción de selector.

const loginButton = screen.getByText(/login/i,  { selector:  'button'  })

Para este caso simple, este enfoque está bien. Pero esto podría causar problemas si se utilizan clases CSS como selector. Debido a que el selector ByRole es más explícita, reduce la posibilidad de encontrar varios elementos.

Si echamos un vistazo a la opción del selector, se parece a la consulta ByRole pero forzado usando con el getBy y selector. El selector ByRole es más robusta ya que no es demasiado específica, ejemplo: es posible encontrar etiquetas de encabezado con 'heading' en lugar de las etiquetas h1 a h6.

TIPS PARA ENCONTRAR EL SELECTOR CORRECTO

Lamentablemente, el *ByRole no es la solución a todos tus problemas. Hay escenarios en los que no se puede utilizar la consulta ByRole. Un ejemplo de esto son los elementos sin un rol, ejemplo. un campo de tipo password. Pero no nos preocuparemos, los selectores originales aún se pueden usar y también se aseguran de que la aplicación sea accesible.

Para encontrar el selector que necesita, hay varios recursos para usar.

La documentación

El sitio web de Testing Library tiene su propia página sobre los selectores. Si no has leído esto, te recomiendo que lo leas. Es un breve resumen, pero definitivamente lo ayudará a escribir mejores tests.

Testing Library Playground

Stephan Meijer creó testing-playground para ayudar a encontrar el "mejor" selector disponible. Para usarlo, copie y pegue el html en el editor y haga clic en los elementos representados o escriba los querys usted mismo.

También esta disponible la extension Chrome y Firefox

Método configure de Testing Library

Recientemente se refactorizaron los ejemplos de Angular Testing Library para hacer uso de los selectores ByRole. Hice esto usando la opción recién agregada throwSuggestions al método de configure. Esta desactivado por defecto, pero es posible activarlo a nivel global o por selector. Como su nombre lo indica, arrojará un error si hay una consulta mejor/más segura disponible.

Como ejemplo, podemos tomar el siguiente HTML y hacer el test de hacer clic en el botón de Increment.

<button (click)="value = value - 1">Decrement</button>  
<span data-testid="value">{{ value }}</span>  
<button (click)="value = value + 1">Increment</button>
import  { render, screen, fireEvent, configure }  from  '@testing-library/angular'  
import  { CounterComponent }  from  './counter.component'  
	
	beforeEach(()  =>  {  
		configure({ throwSuggestions:  true,  }) 
	})  
	test('renders the current value and can increment',  async  ()  =>  {  
		await  render(CounterComponent)  
		const incrementControl = screen.getByText('Increment')
		fireEvent.click(incrementControl) 
	})

Debido a que throwSuggestions está activado, obtenemos el siguiente error mientras ejecutamos él test.

TestingLibraryElementError: A better query is available, try this: 
getByRole("button", {name: /increment/i})

Testing Library nos brinda un mejor selector que podemos copiar y pegar en el test para reemplazar el uso de selectores "incorrectos".

test('renders the current value and can increment',  
async  ()  =>  {  
	await  render(CounterComponent)  
	const incrementControl = screen.getByRole('button', { name: /increment/i }) 
	fireEvent.click(incrementControl) 
})

¿Simple verdad? Para activarlo o desactivarlo para un selector específico podemos hacer lo siguiente, que tendrá el mismo resultado.

    test('renders the current value and can increment', async () => {
      await render(CounterComponent)
     const incrementControl = screen.getByText('Increment', { suggest: true })  
     fireEvent.click(incrementControl)
    })

Mensajes de Testing Library

Testing Library muestra un mensaje útil cuando no encuentra un elemento en el selector ByRole . Veamos el ejemplo a continuación, donde cada elemento accesible se registra con su selector correspondiente.

TestingLibraryElementError: Unable to find an accessible element with the role "textbox" and name `/increment/i`
Here are the accessible roles:

    button:

    Name "Decrement":
    <button />

    Name "Increment":
    <button />

    Name "Login":
    <button />

    --------------------------------------------------
    heading:

    Name "Login now":
    <h3 />

    --------------------------------------------------

TIPS PARA ANGULAR TESTING LIBRARY

screen

En versiones anteriores de Angular Testing Library, sólo era posible consultar elementos a través de objectos devueltos por el método render. En la versión 9 de Angular Testing Library, se exporta un objeto screen que tiene todos los selectores disponibles.

Esto tiene como beneficio que tus tests serán más simples. Un segundo beneficio es que también busca elementos fuera del HTML del componente. Esto puede ser particularmente útil si estás usando Angular Material porque añadirán elementos al cuerpo del documento y fuera del árbol del componente.

import { screen } from '@testing-library/angular'

test('renders the current value and can increment', async () => {
-  const { getByRole } = await render(CounterComponent)
+  await render(CounterComponent)

-  const incrementControl = getByRole('button', { name: /increment/i })
+  const incrementControl = screen.getByRole('button', { name: /increment/i })
})

fireEvent

Para disparar eventos se recomienda utilizar el nuevo objeto fireEvent` en lugar de utilizar los eventos devueltos por el método render. Los eventos disparados por fireEvent también ejecutarán un ciclo de detección de cambios, al igual que antes.

import { fireEvent } from '@testing-library/angular'

test('renders the current value and can increment', async () => {
-  const { click } = await render(CounterComponent)
+  await render(CounterComponent)

-  click(incrementControl)
+  fireEvent.click(incrementControl)
})

find

La versión 9 Testing Library, también arregla los selectores de búsqueda e invocará un ciclo de detección de cambios antes de invocar la consulta. Esto puede ser útil para el código asíncrono que impacta el DOM. Por ejemplo, si input de texto modificado y va a renderizar algo después. Mientras que antes había que llamar manualmente a detectChanges.

- test('shows the load button after text input', fakeAsync(async () => {
+ test('shows the load button after text input', async () => {
  const { detectChanges } = await render(FixtureComponent, {
    imports: [ReactiveFormsModule],
  });

  userEvent.type(screen.getByRole('textbox'), 'What a great day!');

-  tick(200);
-  detectChanges();
-  screen.getByRole('button');
+  screen.findByRole('button');
-}));
+});

Extras

Kent C. Dodds escribió Errores comunes con React Testing Library , la mayoría de los consejos también se aplican a Angular Testing Library. Así que, ¡léanlo para obtener más consejos útiles para escribir sus test con Testing Library!

Nota: Si te ha gustado compartelo.
Plataforma de cursos gratis sobre programación