Del zeitgeist de la pasada Conferencia Rails me quedaría con la idea de que el testing de nuestra aplicación es, no ya posible, sino inexcusable. Así que llevo unos días dándole vueltas a las herramientas de testing que ofrece Rails, más allá de las conocidas técnicas de pruebas unitarias de las que ya hemos hablado por aquí alguna vez

Así que hoy trataré de indagar un poco qué posibilidades tenemos para probar las vistas, que es un territorio más pastoso y maleable que el mundo de los modelos, más predecibles y modosos. Hasta ahora la gran herramienta disponible para verificar aserciones sobre el HTML generado por nuestra aplicación era assert_tag, pero de la mano de Assaf Harkin, ha llegado un nuevo pistolero a la ciudad llamado assert_select que de hecho ha acabado entrando en el core de Rails) (si bien también está disponible como plugin).

Pruebas del HTML para llamadas GET y POST estándar

Para introducir a este nuevo amiguito vamos a jugar con la clásica aplicación de libros y autores. Tendremos, pues, una base de datos con esta pinta:

1
2
3
4
5
6
7
8
9
10
11
ActiveRecord::Schema.define(:version => 1) do

 create_table "authors", :force => true do |t|
     t.column "name", :string
 end

 create_table "books", :force => true do |t|
    t.column "name",      :string
    t.column "author_id", :integer
 end
end

No hay sorpresas en nuestros modelos:

1
2
3
4
5
6
7
       class Author < ActiveRecord::Base
         has_many :books
       end

       class Book < ActiveRecord::Base
         belongs_to :author
       end

Crearemos un andamiaje con script/generate scaffold Author y ya podemos empezar a jugar con la aplicación con script/server. Si entramos en el controlador authors, podemos dar de alta un par de autores y ver lo que nos devuelve la acción index:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
       <h1>
               Listing authors
       </h1>

       <table>
               <tr><th>Name</th></tr>
               <tr>
                       <td>Isaac Asimov</td>
                       <td><a href="/authors/show/1">Show</a></td>
                       <td><a href="/authors/edit/1">Edit</a></td>
               </tr>
               <tr>
                       <td>Carl Sagan</td>
                       <td><a href="/authors/show/2">Show</a></td>
                       <td><a href="/authors/edit/2">Edit</a></td>
               </tr>
                       (etcétera)
       </table>
</body>

¿Podemos escribir una prueba que nos asegure que la tabla se está construyendo de esta manera y no de otra? La respuesta es que sí. Para ello, necesitamos crear una fixture para nuestros tests con los datos de la tabla de autores que vamos a considerar en nuestras pruebas, por ejemplo:

1
2
3
4
5
6
7
8
9
       first:
         id: 1
         name: Carl Sagan
       another:
         id: 2
         name: Isaac Asimov
       last:
         id: 3
         name: Arthur Clarke

Y ahora entra en acción assert_select. En nuestro archivo con las pruebas funcionales para el controlador de autores test/functional/authors_controller_test.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
       def test_list
         get :list

         assert_response :success
         assert_template 'list'

         assert_select "table" do
           assert_select "tr>th", "Name"
           assert_select "tr:nth-child(2)>td", "Carl Sagan"
           assert_select "tr:nth-child(3)>td", "Isaac Asimov"
           assert_select "tr:nth-child(4)>td", "Arthur Clarke"
         end
       end

Con el primer assert_select nos estamos quedando con el primer elemento table del HTML devuelto al invocar la acción list de nuestro controlador. En el bloque pasado como parámetro las siguientes assert_select actuarán sobre elementos HTML que cuelguen del tag table.

La sintaxis de de los selectores que escogen nodos del árbol de etiquetas de la página no es precisamente sencilla, pero cubre todos los casos posibles. Teneis un chuletario aquí

Selectores CSS

La mayor funcionalidad que nos da assert_select es que que tenemos a nuestra disposición todos los selectores CSS, con lo que quizá podríamos escribir nuestra prueba de una manera más sencilla (y con un ejemplo más parecido al Mundo Real):

1
2
3
 <% for column in Author.content_columns %>
  <td class="author_name"><%=h author.send(column.name) %></td>
 <% end %>

Ahora podemos afinar la selección sin tener que buscar directamente el tag de la tabla.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def test_list_with_css
 get :list

 assert_response :success
 assert_template 'list'

 autores = ["Carl Sagan", "Isaac Asimov", "Arthur Clarke"]

 assert_select ".author_name" do
   autores.each do |name|
     assert_select "td", name
   end
 end
end

Rizando el rizo, también podríamos escribir esto tomando directamente todas las fixtures sin necesidad de tener en una lista los nombres duplicados:

1
2
3
4
5
6
7
8
9
10
11
12
def test_list_with_css_no_array
  get :list

  assert_response :success
  assert_template 'list'

  assert_select ".author_name" do
    Author.find(:all).each do |author|
      assert_select "td", author.name
    end
  end
 end

Con assert_select podemos proteger nuestro código Rails de cambios indeseados (o inesperados) en la plantilla (si por error ponemos .autor_name en la plantilla list.rhtml nuestros tests empezarán a fallar indicándonos que algo va mal antes de que nos llame un cliente preguntando por qué con el último cambio en producción se han dejado de ver los nombres de los clientes en color azul-lapislázuli).

¿Y qué pasa con las llamadas Ajax?

No hay problema, select_tag nos permite también cubrirnos las espaldas. Supongamos que en nuestra vista tenemos ahora un enlace al método books que nos devuelve una lista de los libros para un autor dado, actualizando de manera asíncrona un div en nuestra plantilla:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<% for author in @authors %>
 <tr>
 <% for column in Author.content_columns %>
   <td class="author_name"><%=h author.send(column.name) %></td>
 <% end %>
   <td><%= link_to 'Show', :action => 'show', :id => author %></td>
   <td><%= link_to 'Edit', :action => 'edit', :id => author %></td>
   <td><%= link_to 'Destroy', { :action => 'destroy', :id => author
}, :confirm => 'Are you sure?', :method => "post" %></td>
    <td><%= link_to_remote 'Show books',
                               :url => {:action => 'books', :id => author} %></td>

 </tr>
<% end %>

<div libros>
</div>

La acción books del controlador AuthorController, es tan trivial como

1
2
3
 def books
  @books = Author.find(params[:id]).books
 end

Con la plantilla books.rjs


 page.replace_html 'libros', :partial => 'book', :collection => @books

Y el parcial _book.rhtml:


 <p class="book"><%= book.name %></p>

Creando el fichero de fixtures correspondientes e incluyéndolas con :fixtures 'books' en nuestro authors_controller_test (ojo con este paso que si se olvida para Rails sería como si no hubiésemos definido ningún fixture para nuestras pruebas), pasamos a escribir nuestra prueba:

1
2
3
4
5
6
7
8
9
10
11
 def test_books
  xhr :get, :books, :id => 1

  assert_select_rjs "libros" do
    assert_select ".book" do
      assert_select "p", "Cosmos"
      assert_select "p", "Contact"
      assert_select "p", "Broca's Brain"
    end
  end
 end

Simplemente invocamos assert_select_rjs, indicándole el selector de la parte que se va actualizar con nuestra llamada Ajax (si no se actualizase el HTML del div con id libros, la primera aserción fallaría). Tras eso, podemos proceder que en el caso no-Ajax.

Leave a Reply