1 ¿Por qué escribir pruebas para tus aplicaciones Rails?
Rails hace que sea muy fácil escribir tus pruebas. Comienza generando 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 adhiere a la funcionalidad deseada incluso después de una importante refactorización de código.
Las pruebas de Rails también pueden simular solicitudes de navegador y, por lo tanto, puedes probar la respuesta de tu aplicación sin tener que probarla a través del navegador.
2 Introducción a las pruebas
El soporte de pruebas se ha integrado en la estructura 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 configura para las pruebas desde el principio
Rails crea un directorio test
para ti tan pronto como creas un proyecto de Rails usando rails new
nombre_de_la_aplicación. Si enumeras el contenido 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 los ayudantes de vista, los remitentes de correo y los modelos, respectivamente. El directorio channels
está destinado a contener pruebas para la conexión y los canales de Action Cable. El directorio controllers
está destinado a contener pruebas para los controladores, las rutas y las vistas. El directorio integration
está destinado a contener pruebas para las interacciones entre controladores.
El directorio de pruebas de sistema contiene pruebas de sistema, que se utilizan para probar completamente la aplicación en el navegador. Las pruebas de sistema te permiten probar tu aplicación de la misma manera en que tus usuarios la experimentan y te ayudan a probar tu JavaScript también. Las pruebas de sistema heredan de Capybara y realizan pruebas en el navegador para tu aplicación.
Las fixtures son una forma de organizar los datos de prueba; se encuentran 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 de sistema.
2.2 El entorno de pruebas
Por defecto, cada aplicación de 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 pruebas 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 Getting Started with 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 familiarizarte con el código y la terminología de las pruebas en Rails.
require "test_helper"
Al requerir este archivo, test_helper.rb
, se carga la configuración predeterminada para ejecutar nuestras pruebas. Incluiremos esto en 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
La clase ArticleTest
define un caso de prueba porque hereda de ActiveSupport::TestCase
. ArticleTest
tiene 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 comienza 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 válidos 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 con el prefijo test_
. Por lo tanto, 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:
ruby
def test_the_truth
assert true
end
Aunque aún se pueden usar definiciones de métodos regulares, el uso de la macro test
permite un nombre de prueba más legible.
NOTA: El nombre del método se genera reemplazando los espacios por guiones bajos. El resultado no necesita ser un identificador válido de Ruby, ya que el nombre puede contener caracteres de puntuación, etc. Esto se debe a que en Ruby técnicamente cualquier cadena puede ser un nombre de método. Esto puede requerir el uso de llamadas define_method
y send
para funcionar correctamente, pero formalmente hay pocas restricciones en 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) en busca de resultados esperados. Por ejemplo, una afirmación puede verificar:
- ¿este valor es igual a aquel valor?
- ¿este objeto es nulo?
- ¿esta línea de código genera una excepción?
- ¿la contraseña del usuario tiene más de 5 caracteres?
Cada prueba puede contener una o más afirmaciones, sin restricciones en cuanto a la cantidad de afirmaciones permitidas. Solo cuando todas las afirmaciones tienen éxito, la prueba se aprueba.
2.3.1 Tu primera prueba fallida
Para ver cómo se informa de un fallo en la prueba, puedes agregar una prueba fallida al caso de prueba article_test.rb
.
test "no debería guardar un artículo sin título" 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
Opciones de ejecución: --seed 44656
# Ejecutando:
F
Fallo:
ArticleTest#test_should_not_save_article_without_title [/ruta/al/blog/test/models/article_test.rb:6]:
Se esperaba que true fuera nil o false
bin/rails test test/models/article_test.rb:6
Terminado en 0.023918s, 41.8090 ejecuciones/s, 41.8090 afirmaciones/s.
1 ejecuciones, 1 afirmaciones, 1 fallos, 0 errores, 0 omisiones
En la salida, F
denota un fallo. Puedes ver la traza correspondiente mostrada bajo Fallo
junto con el nombre de la prueba fallida. Las siguientes líneas contienen la traza de la pila seguida de un mensaje que menciona el valor real y el valor esperado por la afirmación. Los mensajes de fallo de afirmación predeterminados proporcionan suficiente información para ayudar a localizar 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 "no debería guardar un artículo sin título" do
article = Article.new
assert_not article.save, "Se guardó el artículo sin título"
end
Al ejecutar esta prueba, se muestra el mensaje de fallo de afirmación más amigable:
Fallo:
ArticleTest#test_should_not_save_article_without_title [/ruta/al/blog/test/models/article_test.rb:6]:
Se guardó el artículo sin título
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
Opciones de ejecución: --seed 31252
# Ejecutando:
.
Terminado en 0.027476s, 36.3952 ejecuciones/s, 36.3952 afirmaciones/s.
1 ejecuciones, 1 afirmaciones, 0 fallos, 0 errores, 0 omisiones
Ahora, si te diste cuenta, primero escribimos una prueba que falla para una funcionalidad deseada, luego escribimos algún 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 Dirigido por 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 "debería informar un error" do
# some_undefined_variable no está definida en otro lugar 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
Opciones de ejecución: --seed 1808
# Ejecutando:
.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
Terminado en 0.040609s, 49.2500 ejecuciones/s, 24.6250 afirmaciones/s.
2 ejecuciones, 1 afirmaciones, 0 fallos, 1 errores, 0 omisiones
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 cualquier error o fallo de 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 utilizar para configurar el orden de las pruebas.
Cuando una prueba falla, se muestra la traza correspondiente. Por defecto, Rails filtra esa traza y solo imprime las líneas relevantes a tu aplicación. Esto elimina el ruido del framework y ayuda a centrarse en tu código. Sin embargo, hay situaciones en las que deseas ver la traza completa. Establece el argumento -b
(o --backtrace
) para habilitar este comportamiento:
bash
$ bin/rails test -b test/models/article_test.rb
Si queremos que este test pase, podemos modificarlo para usar assert_raises
de la siguiente manera:
test "should report error" do
# some_undefined_variable no está definida en otro lugar en el caso de prueba
assert_raises(NameError) do
some_undefined_variable
end
end
Este test debería pasar ahora.
2.4 Asertos Disponibles
Hasta ahora has visto algunos de los asertos que están disponibles. Los asertos son los trabajadores de las pruebas. Son los que realmente realizan las comprobaciones para asegurarse de que las cosas van según lo planeado.
Aquí tienes un extracto de los asertos que puedes usar con
Minitest
, la biblioteca de pruebas predeterminada
usada por Rails. El parámetro [msg]
es un mensaje de cadena opcional que puedes
especificar para que los mensajes de fallo de tus pruebas sean más claros.
Aserto | Propósito |
---|---|
assert( test, [msg] ) |
Asegura que test sea verdadero. |
assert_not( test, [msg] ) |
Asegura que test sea falso. |
assert_equal( expected, actual, [msg] ) |
Asegura que expected == actual sea verdadero. |
assert_not_equal( expected, actual, [msg] ) |
Asegura que expected != actual sea verdadero. |
assert_same( expected, actual, [msg] ) |
Asegura que expected.equal?(actual) sea verdadero. |
assert_not_same( expected, actual, [msg] ) |
Asegura que expected.equal?(actual) sea falso. |
assert_nil( obj, [msg] ) |
Asegura que obj.nil? sea verdadero. |
assert_not_nil( obj, [msg] ) |
Asegura que obj.nil? sea 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 coincida con la expresión regular. |
assert_no_match( regexp, string, [msg] ) |
Asegura que una cadena no coincida 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 uno del otro. |
assert_not_in_delta( expected, actual, [delta], [msg] ) |
Asegura que los números expected y actual no estén dentro de delta uno del otro. |
assert_in_epsilon ( expected, actual, [epsilon], [msg] ) |
Asegura que los números expected y actual tengan un error relativo menor que epsilon . |
assert_not_in_epsilon ( expected, actual, [epsilon], [msg] ) |
Asegura que los números expected y actual tengan un error relativo no menor que epsilon . |
assert_throws( symbol, [msg] ) { block } |
Asegura que el bloque dado lance el símbolo. |
assert_raises( exception1, exception2, ... ) { block } |
Asegura que el bloque dado lance una de las excepciones dadas. |
assert_instance_of( class, obj, [msg] ) |
Asegura que obj sea una instancia de class . |
assert_not_instance_of( class, obj, [msg] ) |
Asegura que obj no sea una instancia de class . |
assert_kind_of( class, obj, [msg] ) |
Asegura que obj sea una instancia de class o descienda de ella. |
assert_not_kind_of( class, obj, [msg] ) |
Asegura que obj no sea una instancia de class y no descienda de ella. |
assert_respond_to( obj, symbol, [msg] ) |
Asegura que obj responda a symbol . |
assert_not_respond_to( obj, symbol, [msg] ) |
Asegura que obj no responda a symbol . |
assert_operator( obj1, operator, [obj2], [msg] ) |
Asegura que obj1.operator(obj2) sea verdadero. |
assert_not_operator( obj1, operator, [obj2], [msg] ) |
Asegura que obj1.operator(obj2) sea falso. |
assert_predicate ( obj, predicate, [msg] ) |
Asegura que obj.predicate sea verdadero, por ejemplo assert_predicate str, :empty? |
assert_not_predicate ( obj, predicate, [msg] ) |
Asegura que obj.predicate sea falso, por ejemplo assert_not_predicate str, :empty? |
flunk( [msg] ) |
Asegura el fallo. Esto es útil para marcar explícitamente una prueba que aún no está terminada. |
Estos son un subconjunto de los asertos que minitest admite. Para obtener 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 propios asertos. De hecho, eso es exactamente lo que hace Rails. Incluye algunos asertos especializados para facilitar tu vida.
NOTA: Crear tus propios asertos es un tema avanzado que no cubriremos en este tutorial.
2.5 Asertos Específicos de Rails
Rails agrega algunos asertos personalizados a la biblioteca minitest
:
Afirmación | Propósito |
---|---|
assert_difference(expresiones, diferencia = 1, mensaje = 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 proporcionado. |
assert_no_difference(expresiones, mensaje = nil, &bloque) |
Asegura que el resultado numérico de evaluar una expresión no cambie antes y después de invocar el bloque proporcionado. |
assert_changes(expresiones, mensaje = nil, from:, to:, &bloque) |
Prueba que el resultado de evaluar una expresión cambie después de invocar el bloque proporcionado. |
assert_no_changes(expresiones, mensaje = nil, &bloque) |
Prueba que el resultado de evaluar una expresión no cambie después de invocar el bloque proporcionado. |
assert_nothing_raised { bloque } |
Asegura que el bloque dado no genere ninguna excepción. |
assert_recognizes(opciones_esperadas, ruta, extras={}, mensaje=nil) |
Asegura que el enrutamiento de la ruta dada se maneje correctamente y que las opciones analizadas (dadas en el hash opciones_esperadas) coincidan con la ruta. Básicamente, asegura que Rails reconozca la ruta dada por opciones_esperadas. |
assert_generates(ruta_esperada, opciones, defectos={}, extras = {}, mensaje=nil) |
Asegura que las opciones proporcionadas se puedan utilizar para generar la ruta proporcionada. Esto es lo contrario de assert_recognizes. El parámetro extras se utiliza para indicar los nombres y valores de los parámetros de solicitud adicionales que estarían en una cadena de consulta. El parámetro mensaje te permite especificar un mensaje de error personalizado para las fallas de la afirmación. |
assert_response(tipo, mensaje = nil) |
Asegura que la respuesta tenga 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 obtener más información, consulta la lista completa de códigos de estado y cómo funciona su mapeo. |
assert_redirected_to(opciones = {}, mensaje=nil) |
Asegura que la respuesta sea una redirección a una URL que coincida con las opciones proporcionadas. También puedes pasar rutas con nombre como assert_redirected_to root_path y objetos de Active Record como assert_redirected_to @article . |
Verás el uso de algunas de estas afirmaciones en el próximo capítulo.
2.6 Una breve nota sobre 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:
ActiveSupport::TestCase
ActionMailer::TestCase
ActionView::TestCase
ActiveJob::TestCase
ActionDispatch::IntegrationTest
ActionDispatch::SystemTestCase
Rails::Generators::TestCase
Cada una de estas clases incluye Minitest::Assertions
, lo que nos permite usar todas las afirmaciones básicas en nuestras pruebas.
NOTA: Para obtener más información sobre Minitest
, consulta su documentación.
2.7 El ejecutor de pruebas de Rails
Podemos ejecutar todas nuestras pruebas a la vez utilizando el comando bin/rails test
.
O podemos ejecutar un solo archivo de pruebas pasando al comando bin/rails test
el nombre del archivo que contiene los casos de prueba.
$ bin/rails test test/models/article_test.rb
Opciones de ejecución: --seed 1559
# Ejecutando:
..
Finalizado en 0.027034s, 73.9810 ejecuciones/s, 110.9715 afirmaciones/s.
2 ejecuciones, 3 afirmaciones, 0 fallas, 0 errores, 0 omisiones
Esto ejecutará todos los métodos de prueba del caso de prueba.
También puedes ejecutar un método de prueba específico 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
Opciones de ejecución: -n test_the_truth --seed 43583
# Ejecutando:
.
Pruebas finalizadas en 0.009064s, 110.3266 pruebas/s, 110.3266 afirmaciones/s.
1 pruebas, 1 afirmaciones, 0 fallas, 0 errores, 0 omisiones
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 directorio completo de pruebas proporcionando la ruta del 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 detenerse en caso de falla, posponer la salida de las pruebas hasta el final de la ejecución de las pruebas, entre otras. Consulta la documentación del ejecutor de pruebas de la siguiente manera:
$ bin/rails test -h
Uso: rails test [opciones] [archivos o directorios]
Puedes ejecutar una sola prueba agregando un número de línea a un nombre de archivo:
bin/rails test test/models/user_test.rb:27
Puedes ejecutar varios archivos y directorios al mismo tiempo:
bin/rails test test/controllers test/integration/login_test.rb
De forma predeterminada, los errores y fallas de las pruebas se informan en línea durante la ejecución.
Opciones de minitest:
-h, --help Muestra esta ayuda.
--no-plugins Ignora la carga automática de complementos de minitest (o establece $MT_NO_PLUGINS).
-s, --seed SEED Establece la semilla aleatoria. También se puede establecer mediante env. Ej: SEED=n rake
-v, --verbose Detallado. Muestra el progreso al procesar archivos.
-n, --name PATTERN Filtra la ejecución en /regexp/ o cadena.
--exclude PATTERN Excluye /regexp/ o cadena de la ejecución.
Extensiones conocidas: rails, pride
-w, --warnings Ejecuta con advertencias de Ruby habilitadas
-e, --environment ENV Ejecuta las pruebas en el entorno ENV
-b, --backtrace Muestra la traza completa
-d, --defer-output Muestra los errores y fallas de las pruebas después de la ejecución de las pruebas
-f, --fail-fast Aborta la ejecución de las pruebas en caso de la primera falla o error
-c, --[no-]color Habilita el color en la salida
-p, --pride Orgullo. ¡Muestra tu orgullo por las pruebas!
2.8 Ejecución de pruebas en Integración Continua (CI)
Para ejecutar todas las pruebas en un entorno de CI, solo necesitas un comando:
$ bin/rails test
Si estás utilizando 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, incluyendo las pruebas de sistema.
3 Pruebas Paralelas
Las pruebas paralelas te permiten paralelizar tu conjunto de pruebas. Si bien la bifurcación de procesos es el método predeterminado, también se admite el uso de hilos. Ejecutar pruebas en paralelo reduce el tiempo que tarda en ejecutarse todo el conjunto de pruebas.
3.1 Pruebas Paralelas con Procesos
El método de paralelización predeterminado es bifurcar procesos utilizando el sistema DRb de Ruby. Los procesos se bifurcan en función del número de trabajadores proporcionados. El número predeterminado es el número real de núcleos en la máquina en la que te encuentras, pero se puede cambiar mediante 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
Cuando se paralelizan las 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 agregarán un sufijo 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 las pruebas usarán la base de datos original test-database
.
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 utiliza 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 utiliza la prueba paralela con hilos.
3.2 Pruebas Paralelas con Hilos
Si prefieres utilizar hilos o estás utilizando 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 utilizar hilos en lugar de bifurcaciones, agrega lo siguiente a tu test_helper.rb
:
class ActiveSupport::TestCase
parallelize(workers: :number_of_processors, with: :threads)
end
Las aplicaciones de 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 utilizarán las pruebas. 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
Rails envuelve automáticamente cualquier caso de prueba en una transacción de base de datos que se deshace después de que se completa la prueba. Esto hace que los casos de prueba sean independientes entre sí y los cambios en la base de datos solo son visibles dentro de una sola prueba.
Cuando deseas probar código que ejecuta transacciones paralelas en hilos, las transacciones pueden bloquearse entre sí porque ya están anidadas bajo la transacción de prueba.
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 "transacciones paralelas" do
# iniciar algunos hilos que creen transacciones
end
end
NOTA: Con las pruebas transaccionales deshabilitadas, debes limpiar cualquier dato que las pruebas creen, ya que los cambios no se deshacen automáticamente después de que se completa la prueba.
3.4 Umbral para paralelizar pruebas
La ejecución de 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á las ejecuciones que involucren menos de 50 pruebas.
Puedes configurar este umbral en tu test.rb
:
ruby
config.active_support.test_parallelization_threshold = 100
Y también al configurar la paralelización a nivel de caso de prueba:
class ActiveSupport::TestCase
parallelize threshold: 100
end
4 La base de datos de pruebas
Casi todas las aplicaciones de Rails interactúan ampliamente con una base de datos y, como resultado, sus pruebas también necesitarán una base de datos con la que interactuar. Para escribir pruebas eficientes, deberás entender cómo configurar esta base de datos y poblarla con datos de muestra.
Por defecto, cada aplicación de 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 pruebas dedicada te permite configurar e interactuar con datos de prueba de forma aislada. De esta manera, tus pruebas pueden manipular datos de prueba con confianza, sin preocuparse por los datos en las bases de datos de desarrollo o producción.
4.1 Mantener el esquema de la base de datos de pruebas
Para ejecutar tus pruebas, tu base de datos de pruebas deberá tener la estructura actual. El ayudante de pruebas verifica si tu base de datos de pruebas tiene migraciones pendientes. Intentará cargar tu db/schema.rb
o db/structure.sql
en la base de datos de pruebas. Si aún hay migraciones pendientes, se generará un error. Esto suele indicar que tu esquema no está completamente migrado. Ejecutar las migraciones en la base de datos de desarrollo (bin/rails db:migrate
) actualizará el esquema.
NOTA: Si se realizaron modificaciones en migraciones existentes, la base de datos de pruebas debe reconstruirse. Esto se puede hacer ejecutando bin/rails db:test:prepare
.
4.2 Todo sobre los fixtures
Para buenas pruebas, deberás pensar en cómo configurar los datos de prueba. En Rails, puedes hacer 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 muestra. Los fixtures te permiten poblar tu base de datos de pruebas con datos predefinidos antes de que se ejecuten tus pruebas. Los fixtures son independientes de la base de datos y se escriben en YAML. Hay un archivo por modelo.
NOTA: Los fixtures no están diseñados para crear todos los objetos que tus pruebas necesitan, y se gestionan mejor cuando solo se utilizan para datos predeterminados que se pueden aplicar al caso común.
Encontrarás los fixtures en tu directorio test/fixtures
. Cuando ejecutas bin/rails generate model
para crear un nuevo modelo, Rails crea automáticamente stubs de fixtures en este directorio.
4.2.2 YAML
Los fixtures en formato YAML son una forma amigable para describir tus datos de muestra. Estos tipos de fixtures tienen la extensión de archivo .yml (como users.yml
).
Aquí tienes un ejemplo de un archivo de fixture YAML:
# ¡Mira y admira! ¡Soy un comentario YAML!
david:
name: David Heinemeier Hansson
birthday: 1979-10-15
profession: Desarrollo de sistemas
steve:
name: Steve Ross Kellock
birthday: 1974-09-27
profession: chico con teclado
Cada fixture recibe un nombre seguido de una lista indentada de pares clave/valor separados por dos puntos. Los registros suelen separarse por una línea en blanco. Puedes colocar comentarios en un archivo de fixture utilizando 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: Acerca de
# test/fixtures/articles.yml
first:
title: ¡Bienvenido a Rails!
category: about
# test/fixtures/action_text/rich_texts.yml
first_content:
record: first (Article)
name: content
body: <div>Hola, desde <strong>un 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 indica a Active Record que cargue la categoría about
encontrada en fixtures/categories.yml
para el primero, y que Action Text cargue el artículo first
encontrado en fixtures/articles.yml
para el segundo.
NOTA: Para que las asociaciones se refieran entre sí por nombre, puedes utilizar el nombre del fixture en lugar de especificar el atributo id:
en los fixtures asociados. Rails asignará automáticamente una clave primaria para que sea 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, se pueden poblar con fixtures.
Considera un modelo Article
que tiene una imagen asociada como un archivo adjunto thumbnail
, junto con datos de fixture en YAML:
class Article
has_one_attached :thumbnail
end
# test/fixtures/articles.yml
first:
title: Un artículo
Suponiendo que hay un archivo codificado image/png en test/fixtures/files/first.png
, las siguientes entradas de fixture YAML generarán los registros relacionados de 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 ERB'in It Up
ERB te permite incrustar código Ruby dentro de las 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 muestra. 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 desde el directorio test/fixtures
de forma predeterminada. La carga implica tres pasos:
- Eliminar cualquier dato existente de la tabla correspondiente al fixture.
- Cargar los datos del fixture en la tabla.
- Volcar los datos del fixture en un método en caso de que quieras acceder a ellos directamente.
CONSEJO: Para eliminar los datos existentes de la base de datos, Rails intenta deshabilitar los disparadores de integridad referencial (como las claves externas y las restricciones de verificación). Si estás obteniendo errores molestos de permisos al ejecutar pruebas, asegúrate de que el usuario de la base de datos tenga privilegios para deshabilitar estos disparadores en el entorno de prueba. (En PostgreSQL, solo los superusuarios pueden deshabilitar todos los disparadores. Lee más sobre los permisos de PostgreSQL aquí).
4.2.6 Los Fixtures son Objetos de Active Record
Los fixtures son instancias de Active Record. Como se mencionó en el punto #3 anterior, puedes acceder al objeto directamente porque está disponible automáticamente como un método cuyo ámbito es local en el caso de prueba. Por ejemplo:
# esto devolverá el objeto User para el fixture llamado david
users(:david)
# esto devolverá la propiedad id para david
users(:david).id
# también se pueden acceder a los métodos disponibles en la clase User
david = users(:david)
david.call(david.partner)
Para obtener varios 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 Modelos
Las pruebas de modelos se utilizan para probar los diversos modelos de tu aplicación.
Las pruebas de modelos de Rails se almacenan en el directorio test/models
. Rails proporciona un generador para crear un esqueleto de prueba de modelo por 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 modelos no tienen su propia superclase como ActionMailer::TestCase
. En su lugar, heredan de ActiveSupport::TestCase
.
6 Pruebas del Sistema
Las pruebas del sistema te permiten probar las interacciones del usuario con tu aplicación, ejecutando pruebas en un navegador real o sin cabeza. Las pruebas del sistema utilizan Capybara en su interior.
Para crear pruebas del sistema en Rails, utiliza el directorio test/system
en tu aplicación. Rails proporciona un generador para crear un esqueleto de prueba del sistema por ti.
$ bin/rails generate system_test users
invoke test_unit
create test/system/users_test.rb
Así es como se ve una prueba del 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
De forma predeterminada, las pruebas del sistema se ejecutan con el controlador Selenium, utilizando el navegador Chrome y un tamaño de pantalla de 1400x1400. La siguiente sección explica cómo cambiar la configuración predeterminada.
6.1 Cambiar la Configuración Predeterminada
Rails facilita cambiar la configuración predeterminada de las pruebas del sistema. Toda la configuración está abstraída para que puedas centrarte en escribir tus pruebas.
Cuando generas una nueva aplicación o un scaffold, se crea un archivo application_system_test_case.rb
en el directorio de pruebas. Aquí es donde debe residir toda la configuración de tus pruebas del sistema.
Si deseas cambiar la configuración predeterminada, puedes cambiar lo que "impulsa" las pruebas del sistema. Por ejemplo, si 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 se utilizará con Selenium), :screen_size
para cambiar el tamaño de la pantalla para las capturas de pantalla y :options
que se pueden utilizar para establecer opciones admitidas por el controlador.
```ruby
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :firefox end ```
Si desea utilizar un navegador sin cabeza, puede utilizar Headless Chrome o Headless Firefox 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 desea utilizar un navegador remoto, por ejemplo, Headless Chrome en Docker, debe agregar la url
remota a través de options
.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
options = ENV["SELENIUM_REMOTE_URL"].present? ? { url: ENV["SELENIUM_REMOTE_URL"] } : {}
driven_by :selenium, using: :headless_chrome, options: options
end
En este caso, la gema webdrivers
ya no es necesaria. Puede eliminarla por completo o agregar la opción require:
en el archivo Gemfile
.
# ...
group :test do
gem "webdrivers", require: !ENV["SELENIUM_REMOTE_URL"] || ENV["SELENIUM_REMOTE_URL"].empty?
end
Ahora debería obtener una conexión al navegador remoto.
$ SELENIUM_REMOTE_URL=http://localhost:4444/wd/hub bin/rails test:system
Si su aplicación en prueba también se está ejecutando de forma remota, por ejemplo, en 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ía obtener una conexión al navegador y al servidor remotos, independientemente de si se está ejecutando en un contenedor Docker o en CI.
Si su configuración de Capybara requiere más ajustes que los proporcionados por Rails, esta configuración adicional se puede agregar al archivo application_system_test_case.rb
.
Consulte la documentación de Capybara para obtener más configuraciones adicionales.
6.2 Ayudante de captura de pantalla
El ScreenshotHelper
es un ayudante diseñado para capturar capturas de pantalla de sus pruebas. Esto puede ser útil para ver el navegador en el punto en que falló una prueba o para ver las capturas de pantalla más tarde para depurar.
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 sus pruebas para tomar una captura de pantalla del navegador.
6.3 Implementación de una prueba de sistema
Ahora vamos a agregar una prueba de sistema a nuestra aplicación de blog. Demostraremos cómo escribir una prueba de sistema visitando la página de índice y creando un nuevo artículo de blog.
Si utilizó el generador de andamios, se creó automáticamente un esqueleto de prueba de sistema para usted. Si no utilizó el generador de andamios, comience creando un esqueleto de prueba de sistema.
$ bin/rails generate system_test articles
Debería haber creado un marcador de posición de archivo de prueba para nosotros. Con la salida del comando anterior, debería 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.
Ejecute las pruebas de sistema.
$ bin/rails test:system
NOTA: De forma predeterminada, ejecutar bin/rails test
no ejecutará sus pruebas de sistema. Asegúrese de ejecutar bin/rails test:system
para ejecutarlas realmente. También puede ejecutar bin/rails test:all
para ejecutar todas las pruebas, incluidas las pruebas de sistema.
6.3.1 Creación de 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, 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 completará el título y el cuerpo del artículo con el texto especificado. Una vez que se completan 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 nuevo 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 Pruebas para múltiples tamaños de pantalla
Si desea probar tamaños móviles además de probar tamaños de escritorio, puede crear otra clase que herede de ActionDispatch::SystemTestCase
y usarla en su conjunto de pruebas. En este ejemplo, se crea un archivo llamado mobile_system_test_case.rb
en el directorio /test
con la siguiente configuración.
```ruby
require "test_helper"
class MobileSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome, screen_size: [375, 667] end ```
Para utilizar esta configuración, crea una prueba dentro de test/system
que herede de MobileSystemTestCase
.
Ahora puedes probar tu aplicación utilizando diferentes configuraciones.
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 lejos
La belleza de las pruebas de sistema es que son similares a las pruebas de integración en que prueban la interacción del usuario con su controlador, modelo y vista, pero las pruebas de sistema son mucho más robustas y realmente prueban su aplicación como si un usuario real la estuviera utilizando. En el futuro, puedes probar cualquier cosa que el usuario mismo haría en su aplicación, como comentar, eliminar artículos, publicar artículos en borrador, etc.
7 Pruebas de Integración
Las pruebas de integración se utilizan para probar cómo interactúan las diferentes 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, utilizamos el directorio test/integration
de nuestra aplicación. Rails proporciona un generador para crear una estructura básica de prueba de integración.
$ bin/rails generate integration_test user_flows
exists test/integration/
create test/integration/user_flows_test.rb
Esto es lo que parece 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 estén disponibles algunos ayudantes adicionales para usar en nuestras pruebas de integración.
7.1 Ayudantes disponibles para pruebas de integración
Además de los ayudantes de prueba estándar, al heredar de ActionDispatch::IntegrationTest
se incluyen algunos ayudantes adicionales para escribir pruebas de integración. Echemos un breve vistazo a las tres categorías de ayudantes que podemos elegir.
Para lidiar con el ejecutor de pruebas de integración, consulta ActionDispatch::Integration::Runner
.
Cuando realizamos solicitudes, tendremos ActionDispatch::Integration::RequestHelpers
disponibles para su uso.
Si necesitamos modificar la sesión o el estado de nuestra prueba de integración, consulta ActionDispatch::Integration::Session
para obtener ayuda.
7.2 Implementar 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 para crear un nuevo artículo de blog y verificar que todo funcione correctamente.
Comenzaremos generando la estructura básica de nuestra prueba de integración:
$ bin/rails generate integration_test blog_flow
Esto debería haber creado un archivo de prueba 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
Vamos a utilizar 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 mediante la afirmación de la presencia de elementos HTML clave y su contenido.
Cuando visitamos nuestra ruta raíz, deberíamos ver que se renderiza welcome/index.html.erb
para la vista. Por lo tanto, esta afirmación debería pasar.
7.2.1 Creando una Integración de Artículos
¿Qué tal si probamos 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 siguientes 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 posteriores después de una redirección.
Finalmente, podemos afirmar que nuestra respuesta fue exitosa y que nuestro nuevo artículo se puede leer en la página.
7.2.2 Llevándolo más lejos
Pudimos probar con éxito un flujo de trabajo muy pequeño para visitar nuestro blog y crear un nuevo artículo. Si queremos llevar esto más lejos, podríamos agregar pruebas para comentar, eliminar artículos o editar comentarios. Las pruebas de integración son un gran lugar para experimentar con todo tipo de casos de uso para nuestras aplicaciones.
8 Pruebas funcionales para tus controladores
En Rails, probar las diferentes 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 eventualmente 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
Debes probar cosas como:
- ¿La solicitud web fue exitosa?
- ¿El usuario fue redirigido a la página correcta?
- ¿El usuario se autenticó 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 las pruebas funcionales en acción es generar un controlador utilizando el generador de andamios:
$ 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 las pruebas para un recurso "Artículo". Puedes echar un vistazo al archivo articles_controller_test.rb
en el directorio test/controllers
.
Si ya tienes un controlador y solo quieres generar el código de andamio de prueba 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 haya sido exitosa y también asegurando que se haya generado 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 enviará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. Puede establecerse en true para marcar la solicitud como Ajax.as
: para codificar la solicitud con un tipo de contenido diferente.
Todos estos argumentos de palabras clave son opcionales.
Ejemplo: Llamando a la acción :show
para el primer Artículo
, 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 Artículo
, pasando un nuevo texto para el title
en params
, como una solicitud Ajax:
patch article_url(Article.last), params: { article: { title: "actualizado" } }, 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 una 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 agregada a nivel de modelo 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 es increíble!", title: "Hola 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, deberás agregar autorización a cada encabezado de solicitud para que todas las pruebas pasen:
post articles_url, params: { article: { body: "¡Rails es increíble!", title: "Hola Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }
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 las 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 C.R.U.D. típica, 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, nos preocupa más el resultado. Las pruebas de solicitud existen para este caso de uso para hacer que sus pruebas sean más útiles.
8.3 Pruebas de solicitudes XHR (Ajax)
Para probar solicitudes Ajax, puede especificar la opción xhr: true
en los métodos get
, post
, patch
, put
y delete
. Por ejemplo:
test "solicitud Ajax" do
article = articles(:one)
get article_url(article), xhr: true
assert_equal "hola mundo", @response.body
assert_equal "text/javascript", @response.media_type
end
8.4 Los Tres Hashes del Apocalipsis
Después de que se haya realizado y procesado una solicitud, tendrá 3 objetos Hash listos para usar:
cookies
- Cualquier cookie que esté configuradaflash
- Cualquier objeto que esté en el flashsession
- Cualquier objeto que esté en las variables de sesión
Como ocurre con los objetos Hash normales, puede acceder a los valores haciendo referencia a las claves por cadena. También puede hacer referencia a ellos por el nombre del símbolo. Por ejemplo:
flash["gordon"] flash[:gordon]
session["shmession"] session[:shmession]
cookies["are_good_for_u"] cookies[:are_good_for_u]
8.5 Variables de instancia disponibles
Después de realizar una solicitud, también tiene acceso a tres variables de instancia en sus pruebas funcionales:
@controller
- El controlador que procesa la solicitud@request
- El objeto de solicitud@response
- El objeto de respuesta
class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "debería obtener índice" do
get articles_url
assert_equal "index", @controller.action_name
assert_equal "application/x-www-form-urlencoded", @request.media_type
assert_match "Artículos", @response.body
end
end
8.6 Configuración de encabezados y variables CGI
Encabezados HTTP y Variables CGI se pueden pasar como encabezados:
# configurar un encabezado HTTP
get articles_url, headers: { "Content-Type": "text/plain" } # simular la solicitud con encabezado personalizado
# configurar una variable CGI
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simular la solicitud con variable de entorno personalizada
8.7 Pruebas de flash
Notices
Si recuerda de antes, uno de los Tres Hashes del Apocalipsis era flash
.
Queremos agregar un mensaje flash
a nuestra aplicación de blog cada vez que alguien crea exitosamente un nuevo artículo.
Comencemos agregando esta afirmación a nuestra prueba test_should_create_article
:
test "debería crear artículo" do
assert_difference("Article.count") do
post articles_url, params: { article: { title: "Some title" } }
end
assert_redirected_to article_path(Article.last)
assert_equal "El artículo se creó correctamente.", 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
Opciones de ejecución: -n test_should_create_article --seed 32266
# Ejecutando:
F
Terminado en 0.114870s, 8.7055 ejecuciones/s, 34.8220 aserciones/s.
1) Error:
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- esperado
+++ actual
@@ -1 +1 @@
-"El artículo se creó correctamente."
+nil
1 ejecuciones, 4 aserciones, 1 errores, 0 fallos, 0 saltos
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] = "El artículo se creó correctamente."
redirect_to @article
else
render "new"
end
end
Ahora, si ejecutamos nuestras pruebas, deberíamos ver que pasan:
$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Opciones de ejecución: -n test_should_create_article --seed 18981
# Ejecutando:
.
Terminado en 0.081972s, 12.1993 ejecuciones/s, 48.7972 aserciones/s.
1 ejecuciones, 4 aserciones, 0 errores, 0 fallos, 0 saltos
8.8 Poniéndolo todo junto
En este punto, nuestro controlador de Artículos prueba las acciones :index
, :new
y :create
. ¿Qué pasa con el manejo de datos existentes?
Escribamos una prueba para la acción :show
:
test "debería mostrar artículo" do
article = articles(:one)
get article_url(article)
assert_response :success
end
Recuerde de nuestra discusión anterior sobre fixtures, el método articles()
nos dará acceso a nuestros fixtures de Artículos.
¿Qué tal eliminar un artículo existente?
test "debería eliminar artículo" 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 "debería actualizar artículo" do
article = articles(:one)
patch article_url(article), params: { article: { title: "actualizado" } }
assert_redirected_to article_path(article)
# Volver a cargar la asociación para obtener los datos actualizados y asegurarse de que el título se haya actualizado.
article.reload
assert_equal "actualizado", article.title
end
Observe que estamos comenzando a ver cierta duplicación en estas tres pruebas, ambas acceden a los mismos datos de fixture de Artículo. Podemos D.R.Y. esto utilizando los métodos setup
y teardown
proporcionados por ActiveSupport::Callbacks
.
Nuestra prueba ahora debería verse algo como lo siguiente. Ignora las otras pruebas por ahora, las omitimos por brevedad. ```ruby 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á utilizando 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)
# Volver a cargar la asociación para obtener los datos actualizados y asegurarse de que el título esté actualizado.
@article.reload
assert_equal "updated", @article.title
end end ```
Similar a otros callbacks en Rails, los métodos setup
y teardown
también se pueden usar pasando un bloque, lambda o el nombre de un método como símbolo para llamar.
8.9 Test Helpers
Para evitar la duplicación de código, puedes agregar tus propios test helpers. Un buen ejemplo puede ser el helper de inicio de sesión:
# 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 helper 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 helpers están llenando 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), "se esperaba que #{number} fuera múltiplo de 42"
end
end
Estos helpers pueden ser requeridos explícitamente según sea necesario e incluidos según sea necesario
require "test_helper"
require "test_helpers/multiple_assertions"
class NumberTest < ActiveSupport::TestCase
include MultipleAssertions
test "420 es múltiplo de cuarenta y dos" do
assert_multiple_of_forty_two 420
end
end
o pueden seguir siendo incluidos directamente en las clases padre relevantes
# test/test_helper.rb
require "test_helpers/sign_in_helper"
class ActionDispatch::IntegrationTest
include SignInHelper
end
8.9.2 Requerir Helpers de forma anticipada
Puede resultar conveniente requerir helpers de forma anticipada en test_helper.rb
para que tus archivos de prueba tengan acceso implícito a ellos. Esto se puede lograr utilizando globbing, de la siguiente manera
# test/test_helper.rb
Dir[Rails.root.join("test", "test_helpers", "**", "*.rb")].each { |file| require file }
Esto tiene la desventaja de aumentar el tiempo de inicio, en comparación con requerir manualmente solo los archivos necesarios en tus pruebas individuales.
9 Probando Rutas
Al igual que todo lo demás en tu aplicación Rails, puedes probar tus rutas. Las pruebas de rutas se encuentran en test/controllers/
o forman parte de las pruebas de controlador.
NOTA: Si tu aplicación tiene rutas complejas, Rails proporciona varios helpers útiles para probarlas.
Para obtener más información sobre las aserciones de enrutamiento disponibles en Rails, consulta la documentación de la API de ActionDispatch::Assertions::RoutingAssertions
.
10 Probando Vistas
Probar la respuesta a tu solicitud mediante la comprobación de la presencia de elementos HTML clave y su contenido es una forma común de probar las vistas de tu aplicación. Al igual que las pruebas de rutas, las pruebas de vistas se encuentran en test/controllers/
o forman parte de las pruebas de controlador. El método assert_select
te permite consultar elementos HTML de la respuesta utilizando una sintaxis simple pero poderosa.
Hay dos formas de assert_select
:
assert_select(selector, [equality], [message])
asegura que se cumpla la condición de igualdad en los elementos seleccionados a través del selector. El selector puede ser una expresión de selector CSS (String) o una expresión con valores de sustitución.
assert_select(element, selector, [equality], [message])
asegura que se cumpla la condición de igualdad en todos los elementos seleccionados a través del selector a partir del elemento (instancia de Nokogiri::XML::Node
o Nokogiri::XML::NodeSet
) y sus descendientes.
Por ejemplo, puedes verificar el contenido del elemento de título en tu respuesta con:
assert_select "title", "Bienvenido a la Guía de Pruebas de Rails"
También puedes usar bloques anidados de assert_select
para una investigación más profunda.
En el siguiente ejemplo, el assert_select
interno para li.menu_item
se ejecuta dentro de la colección de elementos seleccionados por el bloque externo:
assert_select "ul.navigation" do
assert_select "li.menu_item"
end
Una colección de elementos seleccionados se puede iterar para que assert_select
se pueda llamar por separado para cada elemento.
Por ejemplo, si la respuesta contiene dos listas ordenadas, cada una con cuatro elementos de lista anidados, entonces las siguientes pruebas pasarán:
assert_select "ol" do |elements|
elements.each do |element|
assert_select element, "li", 4
end
end
assert_select "ol" do
assert_select "li", 8
end
Esta afirmación es bastante poderosa. Para un uso más avanzado, consulte su documentación.
10.1 Afirmaciones adicionales basadas en vistas
Hay más afirmaciones que se utilizan principalmente en las pruebas de vistas:
Afirmación | Propósito |
---|---|
assert_select_email |
Te permite hacer afirmaciones sobre el cuerpo de un correo electrónico. |
assert_select_encoded |
Te permite hacer afirmaciones sobre HTML codificado. Esto se hace decodificando el contenido de cada elemento y luego llamando al bloque con todos los elementos sin codificar. |
css_select(selector) o css_select(element, selector) |
Devuelve una matriz de todos los elementos seleccionados por el selector. En la segunda variante, primero coincide con el elemento base y luego intenta hacer coincidir la expresión del selector en cualquiera de sus hijos. Si no hay coincidencias, ambas variantes devuelven una matriz vacía. |
Aquí hay un ejemplo de uso de assert_select_email
:
assert_select_email do
assert_select "small", "Por favor, haz clic en el enlace 'Cancelar suscripción' si deseas darte de baja."
end
11 Pruebas de Helpers
Un helper es solo un módulo simple donde puedes definir métodos que están disponibles en tus vistas.
Para probar los helpers, todo lo que necesitas hacer es verificar que la salida del método helper coincida con lo que esperas. Las pruebas relacionadas con los helpers se encuentran en el directorio test/helpers
.
Dado que tenemos el siguiente helper:
module UsersHelper
def link_to_user(user)
link_to "#{user.first_name} #{user.last_name}", user
end
end
Podemos probar la salida de este método de la siguiente manera:
class UsersHelperTest < ActionView::TestCase
test "debería devolver el nombre completo del usuario" do
user = users(:david)
assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user)
end
end
Además, dado que la clase de prueba se extiende de ActionView::TestCase
, tienes acceso a los métodos helper de Rails como link_to
o pluralize
.
12 Pruebas de tus Mailers
Probar las clases de mailer requiere algunas herramientas específicas para hacer un trabajo exhaustivo.
12.1 Manteniendo al Cartero bajo control
Tus clases de mailer, al igual que cualquier otra parte de tu aplicación Rails, deben ser probadas para asegurarse de que funcionen como se espera.
Los objetivos de probar tus clases de mailer son asegurarse de que:
- los correos electrónicos se estén procesando (creados y enviados)
- el contenido del correo electrónico sea correcto (asunto, remitente, cuerpo, etc.)
- se estén enviando los correos electrónicos correctos en el momento adecuado
12.1.1 Desde todos los ángulos
Hay dos aspectos de probar tu mailer, las pruebas unitarias y las pruebas funcionales. En las pruebas unitarias, ejecutas el mailer de forma aislada con entradas controladas y comparas la salida con un valor conocido (un fixture). En las pruebas funcionales, no pruebas tanto los detalles minuciosos producidos por el mailer; en cambio, pruebas que tus controladores y modelos estén utilizando el mailer de la manera correcta. Pruebas para demostrar que se envió el correo electrónico correcto en el momento adecuado.
12.2 Pruebas Unitarias
Para probar que tu mailer funcione como se espera, puedes usar pruebas unitarias para comparar los resultados reales del mailer con ejemplos predefinidos de lo que debería producirse.
12.2.1 La venganza de los Fixtures
Para fines de prueba unitaria de un mailer, se utilizan fixtures para proporcionar un ejemplo de cómo debería verse la salida. Debido a que estos son correos electrónicos de ejemplo, y no datos de Active Record como los demás fixtures, se mantienen en su propio subdirectorio aparte de los demás fixtures. El nombre del directorio dentro de test/fixtures
corresponde directamente al nombre del mailer. Entonces, para un mailer llamado UserMailer
, los fixtures deben residir en el directorio test/fixtures/user_mailer
.
Si generaste tu mailer, el generador no crea fixtures de stub para las acciones del mailer. Deberás crear esos archivos tú mismo como se describe arriba.
12.2.2 El caso de prueba básico
Aquí hay una prueba unitaria para probar un mailer llamado UserMailer
cuya acción invite
se utiliza para enviar una invitación a un amigo. Es una versión adaptada de la prueba base creada por el generador para una acción invite
.
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Crea el correo electrónico y guárdalo para más afirmaciones
email = UserMailer.create_invite("[email protected]",
"[email protected]", Time.now)
# Envía el correo electrónico, luego prueba que se haya encolado
assert_emails 1 do
email.deliver_now
end
# Prueba que el cuerpo del correo electrónico enviado contenga lo que esperamos
assert_equal ["[email protected]"], email.from
assert_equal ["[email protected]"], email.to
assert_equal "Has sido invitado por [email protected]", email.subject
assert_equal read_fixture("invite").join, email.body.to_s
end
end
En la prueba creamos el correo electrónico y almacenamos el objeto devuelto en la variable email
. Luego nos aseguramos de que se haya enviado (el primer assert), luego, en el segundo conjunto de aserciones, nos aseguramos de que el correo electrónico contenga lo que esperamos. El ayudante read_fixture
se utiliza para leer el contenido de este archivo.
NOTA: email.body.to_s
está presente cuando solo hay una parte (HTML o texto) presente. Si el remitente de correo proporciona ambos, puede probar su accesorio contra partes específicas con email.text_part.body.to_s
o email.html_part.body.to_s
.
Aquí está el contenido del accesorio invite
:
Hola [email protected],
Has sido invitado.
¡Saludos!
Este es el momento adecuado para comprender un poco más sobre cómo escribir pruebas para sus remitentes de correo. La línea ActionMailer::Base.delivery_method = :test
en config/environments/test.rb
establece el método de entrega en el modo de prueba para que el correo electrónico no se entregue realmente (útil para evitar enviar spam a sus usuarios durante las pruebas), sino que se agregue a una matriz (ActionMailer::Base.deliveries
).
NOTA: La matriz ActionMailer::Base.deliveries
solo se restablece automáticamente en las pruebas de ActionMailer::TestCase
y ActionDispatch::IntegrationTest
. Si desea tener una pizarra limpia fuera de estos casos de prueba, puede restablecerla manualmente con: ActionMailer::Base.deliveries.clear
12.2.3 Pruebas de correos electrónicos en cola
Puede usar la aserción assert_enqueued_email_with
para confirmar que el correo electrónico se ha encolado con todos los argumentos del método del remitente de correo y/o parámetros del remitente de correo parametrizados esperados. Esto le permite coincidir con cualquier correo electrónico que se haya encolado con el método deliver_later
.
Al igual que con el caso de prueba básico, creamos el correo electrónico y almacenamos el objeto devuelto en la variable email
. Los siguientes ejemplos incluyen variaciones de pasar argumentos y/o parámetros.
Este ejemplo afirmará que el correo electrónico se ha encolado con los argumentos correctos:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Creamos el correo electrónico y lo almacenamos para más aserciones
email = UserMailer.create_invite("[email protected]", "[email protected]")
# Probamos que el correo electrónico se haya encolado con los argumentos correctos
assert_enqueued_email_with UserMailer, :create_invite, args: ["[email protected]", "[email protected]"] do
email.deliver_later
end
end
end
Este ejemplo afirmará que se ha encolado un remitente de correo con los argumentos con nombre correctos pasando un hash de los argumentos como args
:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Creamos el correo electrónico y lo almacenamos para más aserciones
email = UserMailer.create_invite(from: "[email protected]", to: "[email protected]")
# Probamos que el correo electrónico se haya encolado con los argumentos con nombre correctos
assert_enqueued_email_with UserMailer, :create_invite, args: [{ from: "[email protected]",
to: "[email protected]" }] do
email.deliver_later
end
end
end
Este ejemplo afirmará que se ha encolado un remitente de correo parametrizado con los parámetros y argumentos correctos. Los parámetros del remitente de correo se pasan como params
y los argumentos del método del remitente de correo como args
:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Creamos el correo electrónico y lo almacenamos para más aserciones
email = UserMailer.with(all: "bueno").create_invite("[email protected]", "[email protected]")
# Probamos que el correo electrónico se haya encolado con los parámetros y argumentos correctos del remitente de correo
assert_enqueued_email_with UserMailer, :create_invite, params: { all: "bueno" },
args: ["[email protected]", "[email protected]"] do
email.deliver_later
end
end
end
Este ejemplo muestra una forma alternativa de probar que se ha encolado un remitente de correo parametrizado con los parámetros correctos:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Creamos el correo electrónico y lo almacenamos para más aserciones
email = UserMailer.with(to: "[email protected]").create_invite
# Probamos que el correo electrónico se haya encolado con los parámetros correctos del remitente de correo
assert_enqueued_email_with UserMailer.with(to: "[email protected]"), :create_invite do
email.deliver_later
end
end
end
12.3 Pruebas funcionales y de sistema
Las pruebas unitarias nos permiten probar los atributos del correo electrónico, mientras que las pruebas funcionales y de sistema nos permiten probar si las interacciones del usuario desencadenan adecuadamente el envío del correo electrónico. Por ejemplo, puede verificar que la operación de invitar a un amigo esté enviando un correo electrónico adecuadamente:
# Prueba de integración
require "test_helper"
class UsersControllerTest < ActionDispatch::IntegrationTest
test "invite friend" do
# Asegura la diferencia en ActionMailer::Base.deliveries
assert_emails 1 do
post invite_friend_url, params: { email: "[email protected]" }
end
end
end
# Prueba de sistema
require "test_helper"
class UsersTest < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome
test "inviting a friend" do
visit invite_users_url
fill_in "Email", with: "[email protected]"
assert_emails 1 do
click_on "Invite"
end
end
end
NOTA: El método assert_emails
no está vinculado a un método de entrega en particular y funcionará con correos electrónicos entregados con los métodos deliver_now
o deliver_later
. Si queremos afirmar explícitamente que el correo electrónico se ha encolado, podemos usar los métodos assert_enqueued_email_with
(ejemplos arriba) o assert_enqueued_emails
. Se puede encontrar más información en la documentación aquí.
13 Pruebas de trabajo
Dado que sus trabajos personalizados pueden encolarse en diferentes niveles dentro de su aplicación, deberá probar tanto los trabajos en sí mismos (su comportamiento cuando se encolan) como que otras entidades los encolen correctamente.
13.1 Un caso de prueba básico
Por defecto, cuando genera un trabajo, también se generará una prueba asociada
en el directorio test/jobs
. Aquí hay un ejemplo de prueba con un trabajo de facturación:
require "test_helper"
class BillingJobTest < ActiveJob::TestCase
test "que se cobre la cuenta" do
BillingJob.perform_now(account, product)
assert account.reload.charged_for?(product)
end
end
Esta prueba es bastante simple y solo verifica que el trabajo haya realizado el trabajo esperado.
13.2 Afirmaciones personalizadas y pruebas de trabajos dentro de otros componentes
Active Job incluye una serie de afirmaciones personalizadas que se pueden utilizar para reducir la verbosidad de las pruebas. Para obtener una lista completa de las afirmaciones disponibles, consulte la documentación de la API de ActiveJob::TestHelper
.
Es una buena práctica asegurarse de que sus trabajos se encolen o se realicen correctamente donde los invoque (por ejemplo, dentro de sus controladores). Aquí es donde las afirmaciones personalizadas proporcionadas por Active Job son bastante útiles. Por ejemplo, dentro de un modelo, podría confirmar que se encoló un trabajo:
require "test_helper"
class ProductTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
test "programación de trabajos de facturación" do
assert_enqueued_with(job: BillingJob) do
product.charge(account)
end
assert_not account.reload.charged_for?(product)
end
end
El adaptador predeterminado, :test
, no realiza trabajos cuando se encolan.
Debe indicar cuándo desea que se realicen los trabajos:
require "test_helper"
class ProductTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
test "programación de trabajos de facturación" do
perform_enqueued_jobs(only: BillingJob) do
product.charge(account)
end
assert account.reload.charged_for?(product)
end
end
Todos los trabajos realizados y encolados anteriormente se eliminan antes de que se ejecute cualquier prueba, por lo que puede asumir de manera segura que no se han ejecutado trabajos en el ámbito de cada prueba.
14 Pruebas de Action Cable
Dado que Action Cable se utiliza en diferentes niveles dentro de su aplicación, deberá probar tanto los canales como las clases de conexión en sí mismos, y que otras entidades transmitan mensajes correctos.
14.1 Caso de prueba de conexión
Por defecto, cuando genera una nueva aplicación de Rails con Action Cable, también se genera una prueba para la clase de conexión base (ApplicationCable::Connection
) en el directorio test/channels/application_cable
.
Las pruebas de conexión tienen como objetivo verificar si los identificadores de una conexión se asignan correctamente o si se rechazan solicitudes de conexión incorrectas. Aquí hay un ejemplo:
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
test "se conecta con parámetros" do
# Simula la apertura de una conexión llamando al método `connect`
connect params: { user_id: 42 }
# Puede acceder al objeto Connection a través de `connection` en las pruebas
assert_equal connection.user_id, "42"
end
test "rechaza la conexión sin parámetros" do
# Use el matcher `assert_reject_connection` para verificar que
# la conexión sea rechazada
assert_reject_connection { connect }
end
end
También puede especificar cookies de solicitud de la misma manera que lo hace en las pruebas de integración:
test "se conecta con cookies" do
cookies.signed[:user_id] = "42"
connect
assert_equal connection.user_id, "42"
end
Consulte la documentación de la API de ActionCable::Connection::TestCase
para obtener más información.
14.2 Caso de prueba de canal
Por defecto, cuando genera un canal, también se generará una prueba asociada
en el directorio test/channels
. Aquí hay un ejemplo de prueba con un canal de chat:
require "test_helper"
class ChatChannelTest < ActionCable::Channel::TestCase
test "se suscribe y transmite para la sala" do
# Simula la creación de una suscripción llamando a `subscribe`
subscribe room: "15"
# Puede acceder al objeto Channel a través de `subscription` en las pruebas
assert subscription.confirmed?
assert_has_stream "chat_15"
end
end
Esta prueba es bastante simple y solo verifica que el canal suscribe la conexión a un flujo particular.
También puede especificar los identificadores de conexión subyacentes. Aquí hay un ejemplo de prueba con un canal de notificaciones web:
require "test_helper"
class WebNotificationsChannelTest < ActionCable::Channel::TestCase
test "se suscribe y transmite para el usuario" do
stub_connection current_user: users(:john)
subscribe
assert_has_stream_for users(:john)
end
end
Consulte la documentación de la API de ActionCable::Channel::TestCase
para obtener más información.
14.3 Afirmaciones personalizadas y pruebas de transmisión dentro de otros componentes
Action Cable incluye una serie de afirmaciones personalizadas que se pueden utilizar para reducir la verbosidad de las pruebas. Para obtener una lista completa de las afirmaciones disponibles, consulte la documentación de la API de ActionCable::TestHelper
.
Es una buena práctica asegurarse de que se haya transmitido el mensaje correcto dentro de otros componentes (por ejemplo, dentro de sus controladores). Aquí es donde las afirmaciones personalizadas proporcionadas por Action Cable son bastante útiles. Por ejemplo, dentro de un modelo: ```ruby require "test_helper"
class ProductTest < ActionCable::TestCase test "emitir estado después de cobrar" do assert_broadcast_on("products:#{product.id}", type: "charged") do product.charge(account) end end end ```
Si deseas probar la emisión realizada con Channel.broadcast_to
, debes usar Channel.broadcasting_for
para generar un nombre de flujo subyacente:
# app/jobs/chat_relay_job.rb
class ChatRelayJob < ApplicationJob
def perform(room, message)
ChatChannel.broadcast_to room, text: message
end
end
# test/jobs/chat_relay_job_test.rb
require "test_helper"
class ChatRelayJobTest < ActiveJob::TestCase
include ActionCable::TestHelper
test "emitir mensaje a la sala" do
room = rooms(:all)
assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "¡Hola!") do
ChatRelayJob.perform_now(room, "¡Hola!")
end
end
end
15 Pruebas de Carga Temprana
Normalmente, las aplicaciones no cargan de forma temprana en los entornos development
o test
para acelerar las cosas. Pero sí lo hacen en el entorno production
.
Si algún archivo del proyecto no se puede cargar por cualquier motivo, ¿no sería mejor detectarlo antes de implementarlo en producción, verdad?
15.1 Integración Continua
Si tu proyecto tiene una integración continua en funcionamiento, la carga temprana en CI es una forma sencilla de asegurarse de que la aplicación se carga de forma temprana.
Las integraciones continuas suelen establecer alguna variable de entorno para indicar que la suite de pruebas se está ejecutando allí. Por ejemplo, podría ser CI
:
# config/environments/test.rb
config.eager_load = ENV["CI"].present?
A partir de Rails 7, las aplicaciones generadas recientemente están configuradas de esta manera de forma predeterminada.
15.2 Suites de Pruebas Básicas
Si tu proyecto no tiene integración continua, aún puedes cargar de forma temprana en la suite de pruebas llamando a Rails.application.eager_load!
:
15.2.1 Minitest
require "test_helper"
class ZeitwerkComplianceTest < ActiveSupport::TestCase
test "carga de forma temprana todos los archivos sin errores" do
assert_nothing_raised { Rails.application.eager_load! }
end
end
15.2.2 RSpec
require "rails_helper"
RSpec.describe "Cumplimiento de Zeitwerk" do
it "carga de forma temprana todos los archivos sin errores" do
expect { Rails.application.eager_load! }.not_to raise_error
end
end
16 Recursos de Pruebas Adicionales
16.1 Pruebas de Código Dependiente del Tiempo
Rails proporciona métodos auxiliares integrados que te permiten afirmar que tu código sensible al tiempo funciona como se espera.
El siguiente ejemplo utiliza el ayudante travel_to
:
# Dado que un usuario es elegible para regalar un mes después de registrarse.
user = User.create(name: "Gaurish", activation_date: Date.new(2004, 10, 24))
assert_not user.applicable_for_gifting?
travel_to Date.new(2004, 11, 24) do
# Dentro del bloque `travel_to`, `Date.current` se simula
assert_equal Date.new(2004, 10, 24), user.activation_date
assert user.applicable_for_gifting?
end
# El cambio solo fue visible dentro del bloque `travel_to`.
assert_equal Date.new(2004, 10, 24), user.activation_date
Consulta la referencia de la API ActiveSupport::Testing::TimeHelpers
para obtener más información sobre los ayudantes de tiempo disponibles.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.