Pacuna's Blog

Testeando controladores en Rails (Parte I)

En esta serie de tutoriales mostraré como testear controladores en Ruby on Rails usando varias herramientas que nos facilitarán el trabajo pesado.

Montando el ambiente

Usaré una instalación de Rails 4 en este tutorial. Vamos a crear un simple controlador estilo Restful que manejará un recurso posts. Vamos a hacer esto lo más simple posible para los que recién estén empezando con testing.

Primero crearemos un proyecto saltándonos las pruebas que vienen por defecto. Para esto usamos el argumento -T al hacer rails new

rails new testing_controllers -T

Luego agregaremos las dependencias que utilizaremos para testear en nuestro Gemfile. Utilizaremos dos grupos y los declararemos de la siguiente manera:

group :development, :test do
    gem "rspec-rails", "~> 2.14.0"
    gem "factory_girl_rails", "~> 4.2.1"
end

group :test do
    gem "faker", "~> 1.1.2"
    gem "capybara", "~> 2.1.0"
    gem "database_cleaner", "~> 1.0.1"
    gem "launchy", "~> 2.3.0"
    gem "selenium-webdriver", "~> 2.39.0"
end

Luego ejecutamos

bundle install

Inicializamos nuestro directorio de pruebas con el comando

rails g rspec:install

Las gemas más importantes que hemos instalado son rspec-rails, que nos va a proveer un ambiente del framework de testing Rspec adaptado para Ruby on Rails. Tenemos factory_girl_rails que nos va a ayudar con las Factorías para crear datos en nuestras pruebas. Faker será necesario para crear datos más reales al momento de usar Factory_girl. Algunas de estas gemas no las utilizaremos en este manual pero ten en cuenta que todas ellas te servirán mientras tengas que hacer distintos tipos de testing (integración, funcional, aceptación). Queda como tarea que investigues para que sirven las que no he descrito aquí.

Configuración

Una de las partes más engorrosas al armar tu suite de testing es la configuración. Puede verse complicada, pero generalmente son copias de fragmentos de código que las mismas gemas indican en sus sitios respectivos. Además está configuración sólo debes hacerla una vez y luego puedes escribir tus pruebas con mayor comodidad.

Primero abriremos el archivo app/config/application.rb y agregaremos lo siguiente:

class Application < Rails::Application
    config.generators do |g|

      g.test_framework :rspec,
        fixtures: true,
        view_specs: false,
        helper_specs: false,
        routing_specs: false,
        controller_specs: true,
        request_specs: false
      g.fixture_replacement :factory_girl, dir: "spec/factories"
    end

Con esta configuración le decimos a Rails los archivos de testing que debe generarse al usar el generador para crear controladores, modelos, scaffolding, etc. Por ahora queremos generar specs para controladores y fixtures (ahí es donde utilizaremos Factory_girl)

Luego editaremos el archivo spec/spec_helper.rb agregando la siguientes lineas:

RSpec.configure do |config|

  config.include FactoryGirl::Syntax::Methods

Esta linea nos permitirá usar una sintáxis mas cómoda cuando utilicemos Factory_girl.

Por defecto la base de datos estará configurada para usar sqlite en los ambientes de desarrollo y testing, así que como estamos aprendiendo, no configuraremos otro tipo de base de datos. Recuerda que al usar rspec, rails automáticamente configura el ambiente para usar la base de datos de testing.

Si ahora ejecutas el siguiente comando en el directorio root de tu aplicación

rspec

verás un resultado como este

No examples found.

Finished in 0.00008 seconds
0 examples, 0 failures

Si ves esto está todo correcto. Rspec no ha encontrado pruebas en el directorio spec. (Si no tienes instalado rspec globalmente puedes usar gem install rspec).

Creando la primera prueba

Vamos a usar un poco de TDD aquí y vamos a empezar creando la primera prueba para nuestro controlador de post. Primero crea un directorio dentro de spec llamado controllers. El nombre será posts_controller_spec.rb y agregaremos sólo el siguiente código:

spec/controllers/posts_controller_spec.rb

require 'spec_helper'

describe 'GET #index' do
    it "populates an array of all posts and assign it to @posts" do
        post1, post2 = Post.create(title: 'first post', body: 'body of the first post'), Post.create(title: 'second post', body: 'body of the second post')
        get :index
        expect(assigns(:posts)).to match_array [post1, post2]
    end

    it "renders the :index template" do
        get :index
        expect(response).to render_template(:index)
    end
end

Este código nos va a permitir testear la acción index de nuestro controlador. Si has usado Rspec antes sabrás que los bloques ‘describe’ e ‘it’ nos permiten describir el propósito de nuestra prueba. En este ejemplo primero describimos que queremos testear la acción #index que es llamada mediante un método GET.

La primera prueba crea 2 posts, llamada a la función :index y luego hace una aserción para verificar que se está asignando la variable de instancia @posts y además que debe ser igual al arreglo que contiene a los 2 posts creados anteriormente. Recuerda que cuando ejecutamos estas pruebas, estamos utilizando la base de datos de testing.

Luego el segundo bloque sólo verifica que se esté desplegando el template index. Como vez Rspec junto a Rails nos entregan métodos limpios que nos facilitan tremendamente el testing.

Para ejecutar la prueba ahora ejecuta rspec (desde el root de la aplicación):

rspec

El primer error que nos aparece es:

uninitialized constant PostsController

Nos dice que no existe la clase PostsController. Vamos a crearla y de paso crearemos el resto de clases que necesitamos. Usaremos el generador de rails para acelerar el proceso:

rails g model post title body:text

Y luego

rake db:migrate

Ahora crearemos el controlador

rails g controller posts

Cuando nos pregunte si deseamos sobreescribir el spec para este controlador colocaremos que NO.

Ahora el error que nos sale al ejecutar rspec es:

 Failure/Error: get :index
     ActionController::UrlGenerationError:
       No route matches {:action=>"index", :controller=>"posts"}

Este error se debe a que no hemos declarado una ruta para nuestro resource y la acción no está declarada. Vamos al archivo config/routes.rb y agreguémosla

Rails.application.routes.draw do
    resources :posts
end

También agregaremos la acción index en nuestro controlador:

class PostsController < ApplicationController
    def index
    end
end

El error que verás ahora si ejecutas rspec será:

Failure/Error: get :index
     ActionView::MissingTemplate:

Nos falta crear la vista para acción index. Crea el archivo index.html.erb bajo views/posts

Ejecuta nuevamente rspec y verás el siguiente error

Failure/Error: expect(assigns(:posts)).to match_array [post1, post2]
       expected an array, actual collection was nil

Ahora viene la parte donde corresponde implementar la lógica. El error nos está diciendo que la prueba espera que se asigne la variable de instancia @posts y que debe ser igual a los posts creados que en este caso son todos lo que hay en la base de datos.

Agregamos la lógica más simple posible en una acción index:

class PostsController < ApplicationController
    def index
        @posts = Post.all
    end
end

Al ejecutar rspec verás que las 2 pruebas han pasado de forma exitósa, ya que estamos asignando la variable requerida y se está desplegando la vista :index. Excelente.

Escribamos ahora la prueba para la acción show:

describe 'GET #show' do
        it "assigns the requested post to @post" do
           post = Post.create(title: 'first post', body: 'this is another post') 
           get :show, id: post
           expect(assigns(:post)).to eq post
        end

        it "renders the :show template" do
           post = Post.create(title: 'first post', body: 'this is another post') 
           get :show, id: post
           expect(response).to render_template :show
        end
    end

Bastante similar al anterior, pero con la diferencia de que ahora estamos pasando como parámetro un post. Como vez pasar el parámetro es tan fácil como agregarlo junto al llamado get :show. Rails tomará la instancia de Post entregada y tomará su id por defecto. Luego asignamos este post a la variable de instancia @post y finalmente verificamos que la vista :show sea mostrada.

Para hacer pasar esta prueba debes primero crear la vista views/posts/show.html.erb y luego agregar la siguiente lógica en la función show:

def show
        @post = Post.find(params[:id])
    end

Si ahora ejecutas rspec, verás que todas las pruebas han pasado correctamente.

En la segunda parte mostraré como testear los métodos create, update y destroy. La tercera parte estará dedicada a como podemos reemplazar la creación de objetos a través de Factory girl y de Faker.

Cualquier duda comenta!

View original

#rails

- 1 toasts