Pruebas de Aplicaciones Rails

Esta guía cubre los mecanismos integrados en Rails para probar tu aplicación.

Después de leer esta guía, sabrás:


1 ¿Por qué escribir pruebas para tus aplicaciones Rails?

Rails facilita enormemente la escritura de tus pruebas. Comienza produciendo código de prueba esqueleto mientras creas tus modelos y controladores.

Al ejecutar tus pruebas de Rails puedes asegurarte de que tu código se adhiera a la funcionalidad deseada incluso después de una importante refactorización del código.

Las pruebas de Rails también pueden simular solicitudes de navegador y así puedes probar la respuesta de tu aplicación sin tener que probarla a través de tu navegador.

2 Introducción a las Pruebas

El soporte para pruebas fue integrado en el tejido de Rails desde el principio. No fue una epifanía de "oh! agreguemos soporte para ejecutar pruebas porque son nuevas y geniales".

2.1 Rails se prepara para las pruebas desde el principio

Rails crea un directorio test para ti tan pronto como creas un proyecto Rails usando rails new nombre_de_aplicación. Si listas los contenidos de este directorio, verás:

$ ls -F test
application_system_test_case.rb  controllers/                     helpers/                         mailers/                         system/
channels/                        fixtures/                        integration/                     models/                          test_helper.rb

Los directorios helpers, mailers y models están destinados a contener pruebas para ayudantes de vista, mailers y modelos, respectivamente. El directorio channels está destinado a contener pruebas para la conexión y canales de Action Cable. El directorio controllers está destinado a contener pruebas para controladores, rutas y vistas. El directorio integration está destinado a contener pruebas para interacciones entre controladores.

El directorio de pruebas del sistema contiene pruebas del sistema, que se utilizan para pruebas completas del navegador de tu aplicación. Las pruebas del sistema te permiten probar tu aplicación de la manera en que tus usuarios la experimentan y también te ayudan a probar tu JavaScript. Las pruebas del sistema heredan de Capybara y realizan pruebas en el navegador para tu aplicación.

Los fixtures son una forma de organizar los datos de prueba; residen en el directorio fixtures.

También se creará un directorio jobs cuando se genere por primera vez una prueba asociada.

El archivo test_helper.rb contiene la configuración predeterminada para tus pruebas.

El archivo application_system_test_case.rb contiene la configuración predeterminada para tus pruebas del sistema.

2.2 El Entorno de Pruebas

Por defecto, cada aplicación Rails tiene tres entornos: desarrollo, prueba y producción.

La configuración de cada entorno se puede modificar de manera similar. En este caso, podemos modificar nuestro entorno de prueba cambiando las opciones que se encuentran en config/environments/test.rb.

NOTA: Tus pruebas se ejecutan bajo RAILS_ENV=test.

2.3 Rails se encuentra con Minitest

Si recuerdas, usamos el comando bin/rails generate model en la guía Comenzando con Rails. Creamos nuestro primer modelo, y entre otras cosas, creó plantillas de prueba en el directorio test:

$ bin/rails generate model article title:string body:text
...
create  app/models/article.rb
create  test/models/article_test.rb
create  test/fixtures/articles.yml
...

La plantilla de prueba predeterminada en test/models/article_test.rb se ve así:

require "test_helper"

class ArticleTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

Un examen línea por línea de este archivo te ayudará a orientarte en el código y la terminología de pruebas de Rails.

require "test_helper"

Al requerir este archivo, test_helper.rb, se carga la configuración predeterminada para ejecutar nuestras pruebas. Incluiremos esto con todas las pruebas que escribamos, por lo que cualquier método agregado a este archivo estará disponible para todas nuestras pruebas.

class ArticleTest < ActiveSupport::TestCase
  # ...
end

La clase ArticleTest define un caso de prueba porque hereda de ActiveSupport::TestCase. ArticleTest tiene así todos los métodos disponibles de ActiveSupport::TestCase. Más adelante en esta guía, veremos algunos de los métodos que nos proporciona.

Cualquier método definido dentro de una clase heredada de Minitest::Test (que es la superclase de ActiveSupport::TestCase) que comience con test_ se llama simplemente una prueba. Por lo tanto, los métodos definidos como test_password y test_valid_password son nombres de prueba legales y se ejecutan automáticamente cuando se ejecuta el caso de prueba.

Rails también agrega un método test que toma un nombre de prueba y un bloque. Genera una prueba normal de Minitest::Unit con nombres de método prefijados con test_. Así que no tienes que preocuparte por nombrar los métodos, y puedes escribir algo como:

test "the truth" do
  assert true
end

Lo cual es aproximadamente lo mismo que escribir esto:

def test_the_truth
  assert true
end

Aunque aún puedes usar definiciones de métodos regulares, usar la macro test permite un nombre de prueba más legible.

NOTA: El nombre del método se genera reemplazando espacios con guiones bajos. Sin embargo, el resultado no necesita ser un identificador Ruby válido: el nombre puede contener caracteres de puntuación, etc. Eso se debe a que, técnicamente, en Ruby cualquier cadena puede ser un nombre de método. Esto puede requerir el uso de llamadas a define_method y send para funcionar correctamente, pero formalmente hay pocas restricciones sobre el nombre.

A continuación, veamos nuestra primera afirmación:

assert true

Una afirmación es una línea de código que evalúa un objeto (o expresión) para obtener resultados esperados. Por ejemplo, una afirmación puede verificar:

  • ¿este valor = ese valor?
  • ¿es este objeto nulo?
  • ¿esta línea de código lanza una excepción?
  • ¿es la contraseña del usuario mayor de 5 caracteres?

Cada prueba puede contener una o más afirmaciones, sin restricción sobre cuántas afirmaciones se permiten. Solo cuando todas las afirmaciones son exitosas, la prueba pasará.

2.3.1 Tu Primera Prueba Fallida

Para ver cómo se informa un fallo en la prueba, puedes agregar una prueba fallida al caso de prueba article_test.rb.

test "should not save article without title" do
  article = Article.new
  assert_not article.save
end

Ejecutemos esta prueba recién agregada (donde 6 es el número de línea donde se define la prueba).

$ bin/rails test test/models/article_test.rb:6
Run options: --seed 44656

# Running:

F

Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Expected true to be nil or false


bin/rails test test/models/article_test.rb:6



Finished in 0.023918s, 41.8090 runs/s, 41.8090 assertions/s.

1 runs, 1 assertions, 1 failures, 0 errors, 0 skips

En la salida, F denota un fallo. Puedes ver el rastro correspondiente que se muestra bajo Failure junto con el nombre de la prueba fallida. Las siguientes líneas contienen el rastro de la pila seguido de un mensaje que menciona el valor real y el valor esperado por la afirmación. Los mensajes de afirmación predeterminados proporcionan suficiente información para ayudar a identificar el error. Para hacer que el mensaje de fallo de la afirmación sea más legible, cada afirmación proporciona un parámetro de mensaje opcional, como se muestra aquí:

test "should not save article without title" do
  article = Article.new
  assert_not article.save, "Saved the article without a title"
end

Ejecutar esta prueba muestra el mensaje de afirmación más amigable:

Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Saved the article without a title

Ahora, para que esta prueba pase, podemos agregar una validación a nivel de modelo para el campo title.

class Article < ApplicationRecord
  validates :title, presence: true
end

Ahora la prueba debería pasar. Verifiquemos ejecutando la prueba nuevamente:

$ bin/rails test test/models/article_test.rb:6
Run options: --seed 31252

# Running:

.

Finished in 0.027476s, 36.3952 runs/s, 36.3952 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

Ahora, si te diste cuenta, primero escribimos una prueba que falla para una funcionalidad deseada, luego escribimos un poco de código que agrega la funcionalidad y finalmente nos aseguramos de que nuestra prueba pase. Este enfoque para el desarrollo de software se conoce como Desarrollo Basado en Pruebas (TDD).

2.3.2 Cómo se ve un Error

Para ver cómo se informa un error, aquí hay una prueba que contiene un error:

test "should report error" do
  # some_undefined_variable no está definida en otra parte del caso de prueba
  some_undefined_variable
  assert true
end

Ahora puedes ver aún más salida en la consola al ejecutar las pruebas:

$ bin/rails test test/models/article_test.rb
Run options: --seed 1808

# Running:

.E

Error:
ArticleTest#test_should_report_error:
NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
    test/models/article_test.rb:11:in 'block in <class:ArticleTest>'


bin/rails test test/models/article_test.rb:9



Finished in 0.040609s, 49.2500 runs/s, 24.6250 assertions/s.

2 runs, 1 assertions, 0 failures, 1 errors, 0 skips

Observa la 'E' en la salida. Denota una prueba con error.

NOTA: La ejecución de cada método de prueba se detiene tan pronto como se encuentra un error o un fallo en la afirmación, y el conjunto de pruebas continúa con el siguiente método. Todos los métodos de prueba se ejecutan en orden aleatorio. La opción config.active_support.test_order se puede usar para configurar el orden de las pruebas.

Cuando una prueba falla, se te presenta el rastro correspondiente. Por defecto, Rails filtra ese rastro y solo imprimirá líneas relevantes para tu aplicación. Esto elimina el ruido del marco y ayuda a centrarse en tu código. Sin embargo, hay situaciones en las que deseas ver el rastro completo. Establece el argumento -b (o --backtrace) para habilitar este comportamiento:

$ bin/rails test -b test/models/article_test.rb

Si queremos que esta prueba pase, podemos modificarla para usar assert_raises así:

test "should report error" do
  # some_undefined_variable no está definida en otra parte del caso de prueba
  assert_raises(NameError) do
    some_undefined_variable
  end
end

Esta prueba debería pasar ahora.

2.4 Afirmaciones Disponibles

Hasta ahora has visto un vistazo de algunas de las afirmaciones que están disponibles. Las afirmaciones son las abejas obreras de las pruebas. Son las que realizan realmente las comprobaciones para asegurarse de que las cosas van según lo planeado.

Aquí hay un extracto de las afirmaciones que puedes usar con Minitest, la biblioteca de pruebas predeterminada utilizada por Rails. El parámetro [msg] es un mensaje de cadena opcional que puedes especificar para hacer más claros los mensajes de fallo de tus pruebas.

Afirmación Propósito
assert( test, [msg] ) Asegura que test es verdadero.
assert_not( test, [msg] ) Asegura que test es falso.
assert_equal( expected, actual, [msg] ) Asegura que expected == actual es verdadero.
assert_not_equal( expected, actual, [msg] ) Asegura que expected != actual es verdadero.
assert_same( expected, actual, [msg] ) Asegura que expected.equal?(actual) es verdadero.
assert_not_same( expected, actual, [msg] ) Asegura que expected.equal?(actual) es falso.
assert_nil( obj, [msg] ) Asegura que obj.nil? es verdadero.
assert_not_nil( obj, [msg] ) Asegura que obj.nil? es falso.
assert_empty( obj, [msg] ) Asegura que obj está empty?.
assert_not_empty( obj, [msg] ) Asegura que obj no está empty?.
assert_match( regexp, string, [msg] ) Asegura que una cadena coincide con la expresión regular.
assert_no_match( regexp, string, [msg] ) Asegura que una cadena no coincide con la expresión regular.
assert_includes( collection, obj, [msg] ) Asegura que obj está en collection.
assert_not_includes( collection, obj, [msg] ) Asegura que obj no está en collection.
assert_in_delta( expected, actual, [delta], [msg] ) Asegura que los números expected y actual están dentro de delta entre sí.
assert_not_in_delta( expected, actual, [delta], [msg] ) Asegura que los números expected y actual no están dentro de delta entre sí.
assert_in_epsilon ( expected, actual, [epsilon], [msg] ) Asegura que los números expected y actual tienen un error relativo menor que epsilon.
assert_not_in_epsilon ( expected, actual, [epsilon], [msg] ) Asegura que los números expected y actual no tienen un error relativo menor que epsilon.
assert_throws( symbol, [msg] ) { block } Asegura que el bloque dado lanza el símbolo.
assert_raises( exception1, exception2, ... ) { block } Asegura que el bloque dado lanza una de las excepciones dadas.
assert_instance_of( class, obj, [msg] ) Asegura que obj es una instancia de class.
assert_not_instance_of( class, obj, [msg] ) Asegura que obj no es una instancia de class.
assert_kind_of( class, obj, [msg] ) Asegura que obj es una instancia de class o desciende de ella.
assert_not_kind_of( class, obj, [msg] ) Asegura que obj no es una instancia de class y no desciende de ella.
assert_respond_to( obj, symbol, [msg] ) Asegura que obj responde a symbol.
assert_not_respond_to( obj, symbol, [msg] ) Asegura que obj no responde a symbol.
assert_operator( obj1, operator, [obj2], [msg] ) Asegura que obj1.operator(obj2) es verdadero.
assert_not_operator( obj1, operator, [obj2], [msg] ) Asegura que obj1.operator(obj2) es falso.
assert_predicate ( obj, predicate, [msg] ) Asegura que obj.predicate es verdadero, por ejemplo, assert_predicate str, :empty?
assert_not_predicate ( obj, predicate, [msg] ) Asegura que obj.predicate es falso, por ejemplo, assert_not_predicate str, :empty?
assert_error_reported(class) { block } Asegura que se ha informado de la clase de error, por ejemplo, assert_error_reported IOError { Rails.error.report(IOError.new("Oops")) }
assert_no_error_reported { block } Asegura que no se han informado errores, por ejemplo, assert_no_error_reported { perform_service }
flunk( [msg] ) Asegura el fallo. Esto es útil para marcar explícitamente una prueba que aún no está terminada.

Las anteriores son un subconjunto de las afirmaciones que admite minitest. Para una lista exhaustiva y más actualizada, consulta la documentación de la API de Minitest, específicamente Minitest::Assertions.

Debido a la naturaleza modular del marco de pruebas, es posible crear tus propias afirmaciones. De hecho, eso es exactamente lo que hace Rails. Incluye algunas afirmaciones especializadas para facilitarte la vida.

NOTA: Crear tus propias afirmaciones es un tema avanzado que no cubriremos en este tutorial.

2.5 Afirmaciones Específicas de Rails

Rails agrega algunas afirmaciones personalizadas propias al marco minitest:

Afirmación Propósito
assert_difference(expressions, difference = 1, message = nil) {...} Prueba la diferencia numérica entre el valor de retorno de una expresión como resultado de lo que se evalúa en el bloque generado.
assert_no_difference(expressions, message = nil, &block) Asegura que el resultado numérico de evaluar una expresión no cambie antes y después de invocar el bloque pasado.
assert_changes(expressions, message = nil, from:, to:, &block) Prueba que el resultado de evaluar una expresión cambie después de invocar el bloque pasado.
assert_no_changes(expressions, message = nil, &block) Prueba que el resultado de evaluar una expresión no cambie después de invocar el bloque pasado.
assert_nothing_raised { block } Asegura que el bloque dado no arroja ninguna excepción.
assert_recognizes(expected_options, path, extras={}, message=nil) Asegura que el enrutamiento de la ruta dada se manejó correctamente y que las opciones analizadas (dadas en el hash expected_options) coinciden con la ruta. Básicamente, asegura que Rails reconoce la ruta dada por expected_options.
assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) Asegura que las opciones proporcionadas se puedan usar para generar la ruta proporcionada. Este es el inverso de assert_recognizes. El parámetro extras se usa para indicar los nombres y valores de parámetros de solicitud adicionales que estarían en una cadena de consulta. El parámetro message te permite especificar un mensaje de error personalizado para fallos de afirmación.
assert_response(type, message = nil) Asegura que la respuesta venga con un código de estado específico. Puedes especificar :success para indicar 200-299, :redirect para indicar 300-399, :missing para indicar 404, o :error para coincidir con el rango 500-599. También puedes pasar un número de estado explícito o su equivalente simbólico. Para más información, consulta lista completa de códigos de estado y cómo funciona su mapeo.
assert_redirected_to(options = {}, message=nil) Asegura que la respuesta es una redirección a una URL que coincide con las opciones dadas. También puedes pasar rutas nombradas como assert_redirected_to root_path y objetos Active Record como assert_redirected_to @article.
assert_queries_count(count = nil, include_schema: false, &block) Asegura que &block genera un número int de consultas SQL.
assert_no_queries(include_schema: false, &block) Asegura que &block no genera consultas SQL.
assert_queries_match(pattern, count: nil, include_schema: false, &block) Asegura que &block genera consultas SQL que coinciden con el patrón.
assert_no_queries_match(pattern, &block) Asegura que &block no genera consultas SQL que coinciden con el patrón.

Verás el uso de algunas de estas afirmaciones en el próximo capítulo.

2.6 Una Breve Nota sobre los Casos de Prueba

Todas las afirmaciones básicas como assert_equal definidas en Minitest::Assertions también están disponibles en las clases que usamos en nuestros propios casos de prueba. De hecho, Rails proporciona las siguientes clases para que heredes de ellas:

Cada una de estas clases incluye Minitest::Assertions, lo que nos permite usar todas las afirmaciones básicas en nuestras pruebas.

NOTA: Para más información sobre Minitest, consulta su documentación.

2.7 Transacciones

Por defecto, Rails envuelve automáticamente las pruebas en una transacción de base de datos que se revierte después de que terminan. Esto hace que las pruebas sean independientes entre sí y los cambios en la base de datos solo son visibles dentro de una sola prueba.

class MyTest < ActiveSupport::TestCase
  test "newly created users are active by default" do
    # Dado que la prueba está implícitamente envuelta en una transacción de base de datos, el usuario
    # creado aquí no será visto por otras pruebas.
    assert User.create.active?
  end
end

El método ActiveRecord::Base.current_transaction aún funciona como se espera, sin embargo:

class MyTest < ActiveSupport::TestCase
  test "current_transaction" do
    # La transacción implícita alrededor de las pruebas no interfiere con la
    # semántica de nivel de aplicación de current_transaction.
    assert User.current_transaction.blank?
  end
end

Si hay múltiples bases de datos de escritura en su lugar, las pruebas se envuelven en tantas transacciones respectivas, y todas ellas se revierten.

2.7.1 Optar por No Usar Transacciones de Prueba

Los casos de prueba individuales pueden optar por no usarlas:

class MyTest < ActiveSupport::TestCase
  # No hay transacción de base de datos implícita que envuelva las pruebas en este caso de prueba.
  self.use_transactional_tests = false
end

2.8 El Ejecutor de Pruebas de Rails

Podemos ejecutar todas nuestras pruebas a la vez usando el comando bin/rails test.

O podemos ejecutar un solo archivo de prueba pasando el comando bin/rails test el nombre del archivo que contiene los casos de prueba.

$ bin/rails test test/models/article_test.rb
Run options: --seed 1559

# Running:

..

Finished in 0.027034s, 73.9810 runs/s, 110.9715 assertions/s.

2 runs, 3 assertions, 0 failures, 0 errors, 0 skips

Esto ejecutará todos los métodos de prueba del caso de prueba.

También puedes ejecutar un método de prueba particular del caso de prueba proporcionando la bandera -n o --name y el nombre del método de prueba.

$ bin/rails test test/models/article_test.rb -n test_the_truth
Run options: -n test_the_truth --seed 43583

# Running:

.

Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

También puedes ejecutar una prueba en una línea específica proporcionando el número de línea.

$ bin/rails test test/models/article_test.rb:6 # ejecutar prueba específica y línea

También puedes ejecutar un rango de pruebas proporcionando el rango de líneas.

$ bin/rails test test/models/article_test.rb:6-20 # ejecuta pruebas desde la línea 6 hasta la 20

También puedes ejecutar un directorio completo de pruebas proporcionando la ruta al directorio.

$ bin/rails test test/controllers # ejecutar todas las pruebas de un directorio específico

El ejecutor de pruebas también proporciona muchas otras características como fallar rápido, diferir la salida de las pruebas al final de la ejecución de la prueba, etc. Consulta la documentación del ejecutor de pruebas de la siguiente manera:

$ bin/rails test -h
Usage:
  bin/rails test [PATHS...]

Run tests except system tests

Examples:
    You can run a single test by appending a line number to a filename:

        bin/rails test test/models/user_test.rb:27

    You can run multiple tests with in a line range by appending the line range to a filename:

        bin/rails test test/models/user_test.rb:10-20

    You can run multiple files and directories at the same time:

        bin/rails test test/controllers test/integration/login_test.rb

    By default test failures and errors are reported inline during a run.

minitest options:
    -h, --help                       Display this help.
        --no-plugins                 Bypass minitest plugin auto-loading (or set $MT_NO_PLUGINS).
    -s, --seed SEED                  Sets random seed. Also via env. Eg: SEED=n rake
    -v, --verbose                    Verbose. Show progress processing files.
    -q, --quiet                      Quiet. Show no progress processing files.
        --show-skips                 Show skipped at the end of run.
    -n, --name PATTERN               Filter run on /regexp/ or string.
        --exclude PATTERN            Exclude /regexp/ or string from run.
    -S, --skip CODES                 Skip reporting of certain types of results (eg E).

Known extensions: rails, pride
    -w, --warnings                   Run with Ruby warnings enabled
    -e, --environment ENV            Run tests in the ENV environment
    -b, --backtrace                  Show the complete backtrace
    -d, --defer-output               Output test failures and errors after the test run
    -f, --fail-fast                  Abort test run on first failure or error
    -c, --[no-]color                 Enable color in the output
        --profile [COUNT]            Enable profiling of tests and list the slowest test cases (default: 10)
    -p, --pride                      Pride. Show your testing pride!

2.9 Ejecutando pruebas en Integración Continua (CI)

Para ejecutar todas las pruebas en un entorno CI, solo hay un comando que necesitas:

$ bin/rails test

Si estás usando Pruebas de Sistema, bin/rails test no las ejecutará, ya que pueden ser lentas. Para ejecutarlas también, agrega otro paso de CI que ejecute bin/rails test:system, o cambia tu primer paso a bin/rails test:all, que ejecuta todas las pruebas incluidas las pruebas del sistema.

3 Pruebas Paralelas

Las pruebas paralelas te permiten paralelizar tu conjunto de pruebas. Si bien el método predeterminado es bifurcar procesos, también se admite el uso de hilos. Ejecutar pruebas en paralelo reduce el tiempo que tarda en ejecutarse todo tu conjunto de pruebas.

3.1 Pruebas Paralelas con Procesos

El método de paralelización predeterminado es bifurcar procesos usando el sistema DRb de Ruby. Los procesos se bifurcan según el número de trabajadores proporcionados. El número predeterminado es el recuento real de núcleos en la máquina en la que te encuentras, pero se puede cambiar por el número pasado al método parallelize.

Para habilitar la paralelización, agrega lo siguiente a tu test_helper.rb:

class ActiveSupport::TestCase
  parallelize(workers: 2)
end

El número de trabajadores pasado es el número de veces que se bifurcará el proceso. Es posible que desees paralelizar tu conjunto de pruebas local de manera diferente a tu CI, por lo que se proporciona una variable de entorno para poder cambiar fácilmente el número de trabajadores que debe usar una ejecución de prueba:

$ PARALLEL_WORKERS=15 bin/rails test

Al paralelizar pruebas, Active Record maneja automáticamente la creación de una base de datos y la carga del esquema en la base de datos para cada proceso. Las bases de datos se sufijarán con el número correspondiente al trabajador. Por ejemplo, si tienes 2 trabajadores, las pruebas crearán test-database-0 y test-database-1 respectivamente.

Si el número de trabajadores pasado es 1 o menos, los procesos no se bifurcarán y las pruebas no se paralelizarán y usarán la base de datos test-database original.

Se proporcionan dos ganchos, uno se ejecuta cuando se bifurca el proceso y otro se ejecuta antes de que se cierre el proceso bifurcado. Estos pueden ser útiles si tu aplicación usa múltiples bases de datos o realiza otras tareas que dependen del número de trabajadores.

El método parallelize_setup se llama justo después de que se bifurcan los procesos. El método parallelize_teardown se llama justo antes de que se cierren los procesos.

class ActiveSupport::TestCase
  parallelize_setup do |worker|
    # configurar bases de datos
  end

  parallelize_teardown do |worker|
    # limpiar bases de datos
  end

  parallelize(workers: :number_of_processors)
end

Estos métodos no son necesarios ni están disponibles cuando se utilizan pruebas paralelas con hilos.

3.2 Pruebas Paralelas con Hilos

Si prefieres usar hilos o estás usando JRuby, se proporciona una opción de paralelización con hilos. El paralelizador con hilos está respaldado por el Parallel::Executor de Minitest.

Para cambiar el método de paralelización para usar hilos en lugar de bifurcaciones, coloca lo siguiente en tu test_helper.rb:

class ActiveSupport::TestCase
  parallelize(workers: :number_of_processors, with: :threads)
end

Las aplicaciones Rails generadas desde JRuby o TruffleRuby incluirán automáticamente la opción with: :threads.

El número de trabajadores pasado a parallelize determina el número de hilos que las pruebas usarán. Es posible que desees paralelizar tu conjunto de pruebas local de manera diferente a tu CI, por lo que se proporciona una variable de entorno para poder cambiar fácilmente el número de trabajadores que debe usar una ejecución de prueba:

$ PARALLEL_WORKERS=15 bin/rails test

3.3 Pruebas de Transacciones Paralelas

Cuando deseas probar el código que ejecuta transacciones de base de datos paralelas en hilos, estas pueden bloquearse entre sí porque ya están anidadas bajo la transacción implícita de prueba.

Para solucionar esto, puedes deshabilitar las transacciones en una clase de caso de prueba configurando self.use_transactional_tests = false:

class WorkerTest < ActiveSupport::TestCase
  self.use_transactional_tests = false

  test "parallel transactions" do
    # iniciar algunos hilos que crean transacciones
  end
end

NOTA: Con pruebas transaccionales deshabilitadas, debes limpiar cualquier dato que las pruebas creen ya que los cambios no se revierten automáticamente después de que la prueba se completa.

3.4 Umbral para paralelizar pruebas

Ejecutar pruebas en paralelo agrega una sobrecarga en términos de configuración de la base de datos y carga de fixtures. Debido a esto, Rails no paralelizará ejecuciones que involucren menos de 50 pruebas.

Puedes configurar este umbral en tu test.rb:

config.active_support.test_parallelization_threshold = 100

Y también al configurar la paralelización a nivel del caso de prueba:

class ActiveSupport::TestCase
  parallelize threshold: 100
end

4 La Base de Datos de Prueba

Casi todas las aplicaciones Rails interactúan mucho con una base de datos y, como resultado, tus pruebas necesitarán una base de datos para interactuar también. Para escribir pruebas eficientes, necesitarás entender cómo configurar esta base de datos y poblarla con datos de ejemplo.

Por defecto, cada aplicación Rails tiene tres entornos: desarrollo, prueba y producción. La base de datos para cada uno de ellos se configura en config/database.yml.

Una base de datos de prueba dedicada te permite configurar e interactuar con datos de prueba de forma aislada. De esta manera, tus pruebas pueden manipular los datos de prueba con confianza, sin preocuparse por los datos en las bases de datos de desarrollo o producción.

4.1 Manteniendo el Esquema de la Base de Datos de Prueba

Para ejecutar tus pruebas, tu base de datos de prueba necesitará tener la estructura actual. El ayudante de prueba verifica si tu base de datos de prueba tiene alguna migración pendiente. Intentará cargar tu db/schema.rb o db/structure.sql en la base de datos de prueba. Si las migraciones aún están pendientes, se generará un error. Por lo general, esto indica que tu esquema no está completamente migrado. Ejecutar las migraciones contra la base de datos de desarrollo (bin/rails db:migrate) actualizará el esquema.

NOTA: Si hubo modificaciones en las migraciones existentes, la base de datos de prueba necesita ser reconstruida. Esto se puede hacer ejecutando bin/rails db:test:prepare.

4.2 Lo Básico sobre los Fixtures

Para buenas pruebas, necesitarás pensar en cómo configurar los datos de prueba. En Rails, puedes manejar esto definiendo y personalizando fixtures. Puedes encontrar documentación completa en la documentación de la API de Fixtures.

4.2.1 ¿Qué son los Fixtures?

Fixtures es una palabra elegante para datos de ejemplo. Los fixtures te permiten poblar tu base de datos de prueba con datos predefinidos antes de que se ejecuten tus pruebas. Los fixtures son independientes de la base de datos y están escritos en YAML. Hay un archivo por modelo.

NOTA: Los fixtures no están diseñados para crear cada objeto que tus pruebas necesiten, y se manejan mejor cuando solo se usan para datos predeterminados que se pueden aplicar al caso común.

Encontrarás fixtures bajo tu directorio test/fixtures. Cuando ejecutas bin/rails generate model para crear un nuevo modelo, Rails crea automáticamente plantillas de fixtures en este directorio.

4.2.2 YAML

Los fixtures formateados en YAML son una forma amigable para describir tus datos de ejemplo. Estos tipos de fixtures tienen la extensión de archivo .yml (como en users.yml).

Aquí hay un archivo de fixture YAML de ejemplo:

# ¡mira y asómbrate! ¡Soy un comentario YAML!
david:
  name: David Heinemeier Hansson
  birthday: 1979-10-15
  profession: Systems development

steve:
  name: Steve Ross Kellock
  birthday: 1974-09-27
  profession: guy with keyboard

Cada fixture se le da un nombre seguido de una lista indentada de pares clave/valor separados por dos puntos. Los registros se separan típicamente por una línea en blanco. Puedes colocar comentarios en un archivo de fixture usando el carácter # en la primera columna.

Si estás trabajando con asociaciones, puedes definir un nodo de referencia entre dos fixtures diferentes. Aquí tienes un ejemplo con una asociación belongs_to/has_many:

# test/fixtures/categories.yml
about:
  name: About
# test/fixtures/articles.yml
first:
  title: Welcome to Rails!
  category: about
# test/fixtures/action_text/rich_texts.yml
first_content:
  record: first (Article)
  name: content
  body: <div>Hello, from <strong>a fixture</strong></div>

Observa que la clave category del primer Artículo encontrado en fixtures/articles.yml tiene un valor de about, y que la clave record de la entrada first_content encontrada en fixtures/action_text/rich_texts.yml tiene un valor de first (Article). Esto sugiere a Active Record cargar la Categoría about encontrada en fixtures/categories.yml para el primero, y a Action Text cargar el Artículo first encontrado en fixtures/articles.yml para el último.

NOTA: Para que las asociaciones se refieran entre sí por nombre, puedes usar el nombre del fixture en lugar de especificar el atributo id: en los fixtures asociados. Rails asignará automáticamente una clave primaria para ser consistente entre ejecuciones. Para obtener más información sobre este comportamiento de asociación, lee la documentación de la API de Fixtures.

4.2.3 Fixtures de Archivos Adjuntos

Al igual que otros modelos respaldados por Active Record, los registros de archivos adjuntos de Active Storage heredan de instancias de ActiveRecord::Base y, por lo tanto, pueden ser poblados por fixtures.

Considera un modelo Article que tiene una imagen asociada como un archivo adjunto thumbnail, junto con datos de fixtures en YAML:

class Article < ApplicationRecord
  has_one_attached :thumbnail
end
# test/fixtures/articles.yml
first:
  title: An Article

Suponiendo que hay un archivo codificado como image/png en test/fixtures/files/first.png, las siguientes entradas de fixtures en YAML generarán los registros relacionados ActiveStorage::Blob y ActiveStorage::Attachment:

# test/fixtures/active_storage/blobs.yml
first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
# test/fixtures/active_storage/attachments.yml
first_thumbnail_attachment:
  name: thumbnail
  record: first (Article)
  blob: first_thumbnail_blob

4.2.4 Usando ERB

ERB te permite incrustar código Ruby dentro de plantillas. El formato de fixture YAML se preprocesa con ERB cuando Rails carga los fixtures. Esto te permite usar Ruby para ayudarte a generar algunos datos de ejemplo. Por ejemplo, el siguiente código genera mil usuarios:

<% 1000.times do |n| %>
  user_<%= n %>:
    username: <%= "user#{n}" %>
    email: <%= "user#{n}@example.com" %>
<% end %>

4.2.5 Fixtures en Acción

Rails carga automáticamente todos los fixtures del directorio test/fixtures por defecto. La carga implica tres pasos:

  1. Eliminar cualquier dato existente en la tabla correspondiente al fixture
  2. Cargar los datos del fixture en la tabla
  3. Volcar los datos del fixture en un método en caso de que desees acceder a ellos directamente

CONSEJO: Para eliminar datos existentes de la base de datos, Rails intenta deshabilitar los triggers de integridad referencial (como claves foráneas y restricciones de verificación). Si estás recibiendo errores de permisos molestos al ejecutar pruebas, asegúrate de que el usuario de la base de datos tenga privilegio para deshabilitar estos triggers en el entorno de prueba. (En PostgreSQL, solo los superusuarios pueden deshabilitar todos los triggers. Lee más sobre los permisos de PostgreSQL aquí).

4.2.6 Los Fixtures son Objetos Active Record

Los fixtures son instancias de Active Record. Como se mencionó en el punto #3 arriba, puedes acceder al objeto directamente porque está automáticamente disponible como un método cuyo alcance es local del caso de prueba. Por ejemplo:

# esto devolverá el objeto User para el fixture llamado david
users(:david)

# esto devolverá la propiedad para david llamada id
users(:david).id

# también se pueden acceder a métodos disponibles en la clase User
david = users(:david)
david.call(david.partner)

Para obtener múltiples fixtures a la vez, puedes pasar una lista de nombres de fixtures. Por ejemplo:

# esto devolverá un array que contiene los fixtures david y steve
users(:david, :steve)

5 Pruebas de Modelo

Las pruebas de modelo se utilizan para probar los diversos modelos de tu aplicación.

Las pruebas de modelo de Rails se almacenan en el directorio test/models. Rails proporciona un generador para crear un esqueleto de prueba de modelo para ti.

$ bin/rails generate test_unit:model article title:string body:text
create  test/models/article_test.rb
create  test/fixtures/articles.yml

Las pruebas de modelo no tienen su propia superclase como ActionMailer::TestCase. En su lugar, heredan de ActiveSupport::TestCase.

6 Pruebas de Sistema

Las pruebas de sistema te permiten probar las interacciones del usuario con tu aplicación, ejecutando pruebas en un navegador real o sin cabeza. Las pruebas de sistema utilizan Capybara bajo el capó.

Para crear pruebas de sistema en Rails, usas el directorio test/system en tu aplicación. Rails proporciona un generador para crear un esqueleto de prueba de sistema para ti.

$ bin/rails generate system_test users
      invoke test_unit
      create test/system/users_test.rb

Aquí tienes cómo se ve una prueba de sistema recién generada:

require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  # test "visiting the index" do
  #   visit users_url
  #
  #   assert_selector "h1", text: "Users"
  # end
end

Por defecto, las pruebas de sistema se ejecutan con el controlador Selenium, usando el navegador Chrome, y un tamaño de pantalla de 1400x1400. La siguiente sección explica cómo cambiar la configuración predeterminada.

Por defecto, Rails intentará rescatar de las excepciones planteadas durante las pruebas y responder con páginas de error HTML. Este comportamiento se puede controlar mediante la configuración config.action_dispatch.show_exceptions.

6.1 Cambiando la Configuración Predeterminada

Rails hace que cambiar la configuración predeterminada para las pruebas de sistema sea muy simple. Toda la configuración está abstraída para que puedas centrarte en escribir tus pruebas.

Cuando generas una nueva aplicación o scaffold, se crea un archivo application_system_test_case.rb en el directorio de pruebas. Aquí es donde debe vivir toda la configuración para tus pruebas de sistema.

Si deseas cambiar la configuración predeterminada, puedes cambiar lo que las pruebas de sistema están "impulsadas por". Digamos que deseas cambiar el controlador de Selenium a Cuprite. Primero agrega la gema cuprite a tu Gemfile. Luego, en tu archivo application_system_test_case.rb, haz lo siguiente:

require "test_helper"
require "capybara/cuprite"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :cuprite
end

El nombre del controlador es un argumento requerido para driven_by. Los argumentos opcionales que se pueden pasar a driven_by son :using para el navegador (esto solo será utilizado por Selenium), :screen_size para cambiar el tamaño de la pantalla para capturas de pantalla, y :options que se pueden usar para establecer opciones compatibles con el controlador.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :firefox
end

Si deseas usar un navegador sin cabeza, podrías usar Chrome sin cabeza o Firefox sin cabeza agregando headless_chrome o headless_firefox en el argumento :using.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome
end

Si deseas usar un navegador remoto, por ejemplo, Chrome sin cabeza en Docker, debes agregar la url remota y establecer browser como remoto a través de options.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  url = ENV.fetch("SELENIUM_REMOTE_URL", nil)
  options = if url
    { browser: :remote, url: url }
  else
    { browser: :chrome }
  end
  driven_by :selenium, using: :headless_chrome, options: options
end

Ahora deberías obtener una conexión al navegador remoto.

$ SELENIUM_REMOTE_URL=http://localhost:4444/wd/hub bin/rails test:system

Si tu aplicación en prueba también se está ejecutando de forma remota, por ejemplo, un contenedor Docker, Capybara necesita más información sobre cómo llamar a servidores remotos.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  def setup
    Capybara.server_host = "0.0.0.0" # enlazar a todas las interfaces
    Capybara.app_host = "http://#{IPSocket.getaddress(Socket.gethostname)}" if ENV["SELENIUM_REMOTE_URL"].present?
    super
  end
  # ...
end

Ahora deberías obtener una conexión al navegador y servidor remotos, independientemente de si está ejecutándose en un contenedor Docker o CI.

Si tu configuración de Capybara requiere más configuración de la que proporciona Rails, esta configuración adicional podría agregarse en el archivo application_system_test_case.rb.

Consulta la documentación de Capybara para configuraciones adicionales.

6.2 Ayudante de Captura de Pantalla

El ScreenshotHelper es un ayudante diseñado para capturar capturas de pantalla de tus pruebas. Esto puede ser útil para ver el navegador en el punto en que una prueba falló, o para ver capturas de pantalla más tarde para depuración.

Se proporcionan dos métodos: take_screenshot y take_failed_screenshot. take_failed_screenshot se incluye automáticamente en before_teardown dentro de Rails.

El método de ayuda take_screenshot se puede incluir en cualquier lugar de tus pruebas para tomar una captura de pantalla del navegador.

6.3 Implementando una Prueba de Sistema

Ahora vamos a agregar una prueba de sistema a nuestra aplicación de blog. Demostraremos escribiendo una prueba de sistema visitando la página de índice y creando un nuevo artículo de blog.

Si usaste el generador de scaffold, se creó automáticamente un esqueleto de prueba de sistema para ti. Si no usaste el generador de scaffold, comienza creando un esqueleto de prueba de sistema.

$ bin/rails generate system_test articles

Debería haber creado un archivo de prueba de marcador de posición para nosotros. Con la salida del comando anterior, deberías ver:

      invoke  test_unit
      create    test/system/articles_test.rb

Ahora abramos ese archivo y escribamos nuestra primera afirmación:

require "application_system_test_case"

class ArticlesTest < ApplicationSystemTestCase
  test "viewing the index" do
    visit articles_path
    assert_selector "h1", text: "Articles"
  end
end

La prueba debería ver que hay un h1 en la página de índice de artículos y pasar.

Ejecuta las pruebas del sistema.

$ bin/rails test:system

NOTA: Por defecto, ejecutar bin/rails test no ejecutará tus pruebas de sistema. Asegúrate de ejecutar bin/rails test:system para ejecutarlas realmente. También puedes ejecutar bin/rails test:all para ejecutar todas las pruebas, incluidas las pruebas de sistema.

6.3.1 Creando una Prueba de Sistema de Artículos

Ahora probemos el flujo para crear un nuevo artículo en nuestro blog.

test "should create Article" do
  visit articles_path

  click_on "New Article"

  fill_in "Title", with: "Creating an Article"
  fill_in "Body", with: "Created this article successfully!"

  click_on "Create Article"

  assert_text "Creating an Article"
end

El primer paso es llamar a visit articles_path. Esto llevará la prueba a la página de índice de artículos.

Luego, el click_on "New Article" encontrará el botón "New Article" en la página de índice. Esto redirigirá el navegador a /articles/new.

Luego, la prueba llenará el título y el cuerpo del artículo con el texto especificado. Una vez que se llenen los campos, se hace clic en "Create Article", lo que enviará una solicitud POST para crear el nuevo artículo en la base de datos.

Seremos redirigidos de vuelta a la página de índice de artículos y allí afirmamos que el texto del título del nuevo artículo está en la página de índice de artículos.

6.3.2 Probando para Múltiples Tamaños de Pantalla

Si deseas probar para tamaños móviles además de probar para escritorio, puedes crear otra clase que herede de ActionDispatch::SystemTestCase y usarla en tu suite de pruebas. En este ejemplo, se crea un archivo llamado mobile_system_test_case.rb en el directorio /test con la siguiente configuración.

require "test_helper"

class MobileSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [375, 667]
end

Para usar esta configuración, crea una prueba dentro de test/system que herede de MobileSystemTestCase. Ahora puedes probar tu aplicación usando múltiples configuraciones diferentes.

require "mobile_system_test_case"

class PostsTest < MobileSystemTestCase
  test "visiting the index" do
    visit posts_url
    assert_selector "h1", text: "Posts"
  end
end

6.3.3 Llevándolo Más Allá

La belleza de las pruebas de sistema es que son similares a las pruebas de integración en el sentido de que prueban la interacción del usuario con tu controlador, modelo y vista, pero las pruebas de sistema son mucho más robustas y realmente prueban tu aplicación como si un usuario real la estuviera usando. En el futuro, puedes probar cualquier cosa que el usuario mismo haría en tu aplicación, como comentar, eliminar artículos, publicar artículos de borrador, etc.

7 Pruebas de Integración

Las pruebas de integración se utilizan para probar cómo interactúan varias partes de nuestra aplicación. Generalmente se utilizan para probar flujos de trabajo importantes dentro de nuestra aplicación.

Para crear pruebas de integración en Rails, usamos el directorio test/integration para nuestra aplicación. Rails proporciona un generador para crear un esqueleto de prueba de integración para nosotros.

$ bin/rails generate integration_test user_flows
      exists  test/integration/
      create  test/integration/user_flows_test.rb

Aquí tienes cómo se ve una prueba de integración recién generada:

require "test_helper"

class UserFlowsTest < ActionDispatch::IntegrationTest
  # test "the truth" do
  #   assert true
  # end
end

Aquí la prueba hereda de ActionDispatch::IntegrationTest. Esto hace que algunos ayudantes adicionales estén disponibles para que los usemos en nuestras pruebas de integración.

Por defecto, Rails intentará rescatar de las excepciones planteadas durante las pruebas y responder con páginas de error HTML. Este comportamiento se puede controlar mediante la configuración config.action_dispatch.show_exceptions.

7.1 Ayudantes Disponibles para Pruebas de Integración

Además de los ayudantes de prueba estándar, heredar de ActionDispatch::IntegrationTest viene con algunos ayudantes adicionales disponibles al escribir pruebas de integración. Vamos a presentarnos brevemente a las tres categorías de ayudantes que tenemos para elegir.

Para tratar con el ejecutor de pruebas de integración, consulta ActionDispatch::Integration::Runner.

Al realizar solicitudes, tendremos ActionDispatch::Integration::RequestHelpers disponibles para nuestro uso.

Si necesitamos cargar archivos, echa un vistazo a ActionDispatch::TestProcess::FixtureFile para ayudar.

Si necesitamos modificar la sesión o el estado de nuestra prueba de integración, echa un vistazo a ActionDispatch::Integration::Session para ayudar.

7.2 Implementando una Prueba de Integración

Agreguemos una prueba de integración a nuestra aplicación de blog. Comenzaremos con un flujo de trabajo básico de crear un nuevo artículo de blog, para verificar que todo esté funcionando correctamente.

Comenzaremos generando nuestro esqueleto de prueba de integración:

$ bin/rails generate integration_test blog_flow

Debería haber creado un archivo de prueba de marcador de posición para nosotros. Con la salida del comando anterior, deberíamos ver:

      invoke  test_unit
      create    test/integration/blog_flow_test.rb

Ahora abramos ese archivo y escribamos nuestra primera afirmación:

require "test_helper"

class BlogFlowTest < ActionDispatch::IntegrationTest
  test "can see the welcome page" do
    get "/"
    assert_select "h1", "Welcome#index"
  end
end

Veremos assert_select para consultar el HTML resultante de una solicitud en la sección Pruebas de Vistas a continuación. Se utiliza para probar la respuesta de nuestra solicitud afirmando la presencia de elementos HTML clave y su contenido.

Cuando visitamos nuestra ruta raíz, deberíamos ver welcome/index.html.erb renderizado para la vista. Así que esta afirmación debería pasar.

7.2.1 Creación de Integración de Artículos

¿Qué tal probar nuestra capacidad para crear un nuevo artículo en nuestro blog y ver el artículo resultante?

test "can create an article" do
  get "/articles/new"
  assert_response :success

  post "/articles",
    params: { article: { title: "can create", body: "article successfully." } }
  assert_response :redirect
  follow_redirect!
  assert_response :success
  assert_select "p", "Title:\n  can create"
end

Desglosemos esta prueba para que podamos entenderla.

Comenzamos llamando a la acción :new en nuestro controlador de Artículos. Esta respuesta debería ser exitosa.

Después de esto, hacemos una solicitud post a la acción :create de nuestro controlador de Artículos:

post "/articles",
  params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!

Las dos líneas que siguen a la solicitud son para manejar la redirección que configuramos al crear un nuevo artículo.

NOTA: No olvides llamar a follow_redirect! si planeas hacer solicitudes subsecuentes después de que se realice una redirección.

Finalmente, podemos afirmar que nuestra respuesta fue exitosa y que nuestro nuevo artículo es legible en la página.

7.2.2 Llevándolo Más Allá

Pudimos probar con éxito un flujo de trabajo muy pequeño para visitar nuestro blog y crear un nuevo artículo. Si quisiéramos llevar esto más allá, podríamos agregar pruebas para comentar, eliminar artículos o editar comentarios. Las pruebas de integración son un excelente lugar para experimentar con todo tipo de casos de uso para nuestras aplicaciones.

8 Pruebas Funcionales para tus Controladores

En Rails, probar las diversas acciones de un controlador es una forma de escribir pruebas funcionales. Recuerda que tus controladores manejan las solicitudes web entrantes a tu aplicación y finalmente responden con una vista renderizada. Al escribir pruebas funcionales, estás probando cómo tus acciones manejan las solicitudes y el resultado o respuesta esperada, en algunos casos una vista HTML.

8.1 Qué Incluir en tus Pruebas Funcionales

Deberías probar cosas como:

  • ¿la solicitud web fue exitosa?
  • ¿el usuario fue redirigido a la página correcta?
  • ¿el usuario fue autenticado correctamente?
  • ¿se mostró el mensaje apropiado al usuario en la vista?
  • ¿se mostró la información correcta en la respuesta?

La forma más fácil de ver pruebas funcionales en acción es generar un controlador usando el generador de scaffold:

$ bin/rails generate scaffold_controller article title:string body:text
...
create  app/controllers/articles_controller.rb
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

Esto generará el código del controlador y pruebas para un recurso Article. Puedes echar un vistazo al archivo articles_controller_test.rb en el directorio test/controllers.

Si ya tienes un controlador y solo deseas generar el código de prueba de scaffold para cada una de las siete acciones predeterminadas, puedes usar el siguiente comando:

$ bin/rails generate test_unit:scaffold article
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

Echemos un vistazo a una de esas pruebas, test_should_get_index del archivo articles_controller_test.rb.

# articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_url
    assert_response :success
  end
end

En la prueba test_should_get_index, Rails simula una solicitud en la acción llamada index, asegurándose de que la solicitud fue exitosa y también asegurando que se generó el cuerpo de respuesta correcto.

El método get inicia la solicitud web y llena los resultados en @response. Puede aceptar hasta 6 argumentos:

  • La URI de la acción del controlador que estás solicitando. Esto puede ser en forma de una cadena o un ayudante de ruta (por ejemplo, articles_url).
  • params: opción con un hash de parámetros de solicitud para pasar a la acción (por ejemplo, parámetros de cadena de consulta o variables de artículo).
  • headers: para establecer los encabezados que se pasarán con la solicitud.
  • env: para personalizar el entorno de la solicitud según sea necesario.
  • xhr: si la solicitud es una solicitud Ajax o no. Se puede establecer en true para marcar la solicitud como Ajax.
  • as: para codificar la solicitud con un tipo de contenido diferente.

Todos estos argumentos de palabra clave son opcionales.

Ejemplo: Llamando a la acción :show para el primer Article, pasando un encabezado HTTP_REFERER:

get article_url(Article.first), headers: { "HTTP_REFERER" => "http://example.com/home" }

Otro ejemplo: Llamando a la acción :update para el último Article, pasando texto nuevo para el title en params, como una solicitud Ajax:

patch article_url(Article.last), params: { article: { title: "updated" } }, xhr: true

Un ejemplo más: Llamando a la acción :create para crear un nuevo artículo, pasando texto para el title en params, como solicitud JSON:

post articles_path, params: { article: { title: "Ahoy!" } }, as: :json

NOTA: Si intentas ejecutar la prueba test_should_create_article de articles_controller_test.rb, fallará debido a la validación a nivel de modelo recién agregada y con razón.

Modifiquemos la prueba test_should_create_article en articles_controller_test.rb para que todas nuestras pruebas pasen:

test "should create article" do
  assert_difference("Article.count") do
    post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }
  end

  assert_redirected_to article_path(Article.last)
end

Ahora puedes intentar ejecutar todas las pruebas y deberían pasar.

NOTA: Si seguiste los pasos en la sección Autenticación Básica, necesitarás agregar autorización a cada encabezado de solicitud para que todas las pruebas pasen:

post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }

Por defecto, Rails intentará rescatar de las excepciones planteadas durante las pruebas y responder con páginas de error HTML. Este comportamiento se puede controlar mediante la configuración config.action_dispatch.show_exceptions.

8.2 Tipos de Solicitud Disponibles para Pruebas Funcionales

Si estás familiarizado con el protocolo HTTP, sabrás que get es un tipo de solicitud. Hay 6 tipos de solicitud admitidos en pruebas funcionales de Rails:

  • get
  • post
  • patch
  • put
  • head
  • delete

Todos los tipos de solicitud tienen métodos equivalentes que puedes usar. En una aplicación típica de C.R.U.D. usarás get, post, put y delete con más frecuencia.

NOTA: Las pruebas funcionales no verifican si el tipo de solicitud especificado es aceptado por la acción, estamos más preocupados por el resultado. Las pruebas de solicitud existen para este caso de uso para hacer que tus pruebas sean más significativas.

8.3 Probando Solicitudes XHR (Ajax)

Para probar solicitudes Ajax, puedes especificar la opción xhr: true para los métodos get, post, patch, put y delete. Por ejemplo:

test "ajax request" do
  article = articles(:one)
  get article_url(article), xhr: true

  assert_equal "hello world", @response.body
  assert_equal "text/javascript", @response.media_type
end

8.4 Los Tres Hashes del Apocalipsis

Después de que se ha realizado y procesado una solicitud, tendrás 3 objetos Hash listos para usar:

  • cookies - Cualquier cookie que esté configurada
  • flash - Cualquier objeto que viva en el flash
  • session - Cualquier objeto que viva en las variables de sesión

Como es el caso con los objetos Hash normales, puedes acceder a los valores haciendo referencia a las claves por cadena. También puedes referenciarlos por nombre de símbolo. Por ejemplo:

flash["gordon"]               # o flash[:gordon]
session["shmession"]          # o session[:shmession]
cookies["are_good_for_u"]     # o cookies[:are_good_for_u]

8.5 Variables de Instancia Disponibles

Después de que se realiza una solicitud, también tienes acceso a tres variables de instancia en tus pruebas funcionales:

  • @controller - El controlador que procesa la solicitud
  • @request - El objeto de solicitud
  • @response - El objeto de respuesta
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_url

    assert_equal "index", @controller.action_name
    assert_equal "application/x-www-form-urlencoded", @request.media_type
    assert_match "Articles", @response.body
  end
end

8.6 Estableciendo Encabezados y Variables CGI

Encabezados HTTP y variables CGI se pueden pasar como encabezados:

# establecer un Encabezado HTTP
get articles_url, headers: { "Content-Type": "text/plain" } # simular la solicitud con encabezado personalizado

# establecer una variable CGI
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simular la solicitud con variable de entorno personalizada

8.7 Probando Avisos de flash

Si recuerdas de antes, uno de los Tres Hashes del Apocalipsis era flash.

Queremos agregar un mensaje de flash a nuestra aplicación de blog cada vez que alguien cree con éxito un nuevo Artículo.

Comencemos agregando esta afirmación a nuestra prueba test_should_create_article:

test "should create article" do
  assert_difference("Article.count") do
    post articles_url, params: { article: { title: "Some title" } }
  end

  assert_redirected_to article_path(Article.last)
  assert_equal "Article was successfully created.", flash[:notice]
end

Si ejecutamos nuestra prueba ahora, deberíamos ver un fallo:

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 32266

# Running:

F

Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.

  1) Failure:
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- expected
+++ actual
@@ -1 +1 @@
-"Article was successfully created."
+nil

1 runs, 4 assertions, 1 failures, 0 errors, 0 skips

Implementemos ahora el mensaje flash en nuestro controlador. Nuestra acción :create debería verse así:

def create
  @article = Article.new(article_params)

  if @article.save
    flash[:notice] = "Article was successfully created."
    redirect_to @article
  else
    render "new"
  end
end

Ahora, si ejecutamos nuestras pruebas, deberíamos ver que pasa:

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 18981

# Running:

.

Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.

1 runs, 4 assertions, 0 failures, 0 errors, 0 skips

8.8 Juntándolo Todo

En este punto, nuestras pruebas del controlador de Artículos prueban la acción :index así como las acciones :new y :create. ¿Qué hay de tratar con datos existentes?

Escribamos una prueba para la acción :show:

test "should show article" do
  article = articles(:one)
  get article_url(article)
  assert_response :success
end

Recuerda de nuestra discusión anterior sobre los fixtures, el método articles() nos dará acceso a nuestros datos de fixtures de Artículos.

¿Qué tal eliminar un Artículo existente?

test "should destroy article" do
  article = articles(:one)
  assert_difference("Article.count", -1) do
    delete article_url(article)
  end

  assert_redirected_to articles_path
end

También podemos agregar una prueba para actualizar un Artículo existente.

test "should update article" do
  article = articles(:one)

  patch article_url(article), params: { article: { title: "updated" } }

  assert_redirected_to article_path(article)
  # Recargar la asociación para obtener los datos actualizados y afirmar que el título se actualizó.
  article.reload
  assert_equal "updated", article.title
end

Observa que estamos empezando a ver cierta duplicación en estas tres pruebas, ambas acceden a los mismos datos de fixtures de Artículo. Podemos D.R.Y. esto usando los métodos setup y teardown proporcionados por ActiveSupport::Callbacks.

Nuestra prueba ahora debería verse algo como lo que sigue. Desprecia las otras pruebas por ahora, las estamos dejando fuera por brevedad.

require "test_helper"

class ArticlesControllerTest < ActionDispatch::IntegrationTest
  # llamado antes de cada prueba individual
  setup do
    @article = articles(:one)
  end

  # llamado después de cada prueba individual
  teardown do
    # cuando el controlador está usando caché, puede ser una buena idea restablecerlo después
    Rails.cache.clear
  end

  test "should show article" do
    # Reutilizar la variable de instancia @article de setup
    get article_url(@article)
    assert_response :success
  end

  test "should destroy article" do
    assert_difference("Article.count", -1) do
      delete article_url(@article)
    end

    assert_redirected_to articles_path
  end

  test "should update article" do
    patch article_url(@article), params: { article: { title: "updated" } }

    assert_redirected_to article_path(@article)
    # Recargar la asociación para obtener los datos actualizados y afirmar que el título se actualizó.
    @article.reload
    assert_equal "updated", @article.title
  end
end

Al igual que con otros callbacks en Rails, los métodos setup y teardown también se pueden usar pasando un bloque, lambda o nombre de método como símbolo para llamar.

8.9 Ayudantes de Prueba

Para evitar la duplicación de código, puedes agregar tus propios ayudantes de prueba. El ayudante de inicio de sesión puede ser un buen ejemplo:

# test/test_helper.rb

module SignInHelper
  def sign_in_as(user)
    post sign_in_url(email: user.email, password: user.password)
  end
end

class ActionDispatch::IntegrationTest
  include SignInHelper
end
require "test_helper"

class ProfileControllerTest < ActionDispatch::IntegrationTest
  test "should show profile" do
    # el ayudante ahora es reutilizable desde cualquier caso de prueba de controlador
    sign_in_as users(:david)

    get profile_url
    assert_response :success
  end
end

8.9.1 Usando Archivos Separados

Si encuentras que tus ayudantes están desordenando test_helper.rb, puedes extraerlos en archivos separados. Un buen lugar para almacenarlos es test/lib o test/test_helpers.

# test/test_helpers/multiple_assertions.rb
module MultipleAssertions
  def assert_multiple_of_forty_two(number)
    assert (number % 42 == 0), "expected #{number

Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.

También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.

Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.

Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.