En Rails, probar las diversas acciones de un controlador es una forma de escribir pruebas funcionales. Recuerde que sus controladores manejan las solicitudes web entrantes en su aplicación y finalmente responden con una vista renderizada. Al escribir pruebas funcionales, está probando cómo sus acciones manejan las solicitudes y el resultado esperado o respuesta, que en algunos casos es una vista HTML.
Debe probar cosas como:
- ¿Fue la solicitud web exitosa?
- ¿Fue redirigido el usuario a la página correcta?
- ¿El usuario ha sido autenticado correctamente?
- ¿Fue almacenado el objeto correcto en la plantilla de respuesta?
- ¿Fue apropiado el mensaje mostrado al usuario en la vista?
La forma más fácil de ver las pruebas funcionales en acción es generar un controlador utilizando el generador 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 las pruebas de un recurso(resource) de artículos. Puede echar un vistazo al archivo articles_controller_test.rb
en el directorio test/controllers
.
Si ya tiene un controlador y sólo desea generar el código scaffold de prueba para cada una de las siete acciones predeterminadas, puede utilizar 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 prueba, 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 se realizó correctamente y también asegurándose de que se ha generado el cuerpo de respuesta correcto.
El método get inicia la solicitud web y rellena los resultados en @response
. Puede aceptar hasta 6 argumentos:
La URI de la acción del controlador que está solicitando. Esto puede tener la forma de una cadena o un helper de ruta (por ejemplo, articles_url
).
- La opcion
params:
con un hash de parámetros de petición 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 solicitud según sea necesario.xhr:
si la solicitud es Ajax o no. Se puede establecer en true para marcar la solicitud como Ajax.as:
para codificar la solicitud con diferentes tipos de contenido. Soporta:json
por defecto.
Todos estos argumentos de palabra clave son opcionales.
Ejemplo: Llamando la acción :show
y pasando un id de 12 como params
y configurando el encabezado HTTP_REFERER
:
get article_url, params: { id: 12 }, headers: { "HTTP_REFERER" => "http://example.com/home" }
Otro ejemplo: Llamar a la acción :update
, pasando un id de 12 como params
como una solicitud de Ajax.
patch article_url, params: { id: 12 }, xhr: true
Si intenta ejecutar la prueba
test_should_create_article
desdearticles_controller_test.rb
fallará teniendo en cuenta que de la validación a nivel de modelo ha sido recién agregada y con razón.
Modificemos la prueba test_should_create_article
en articles_controller_test.rb
para que todos nuestros test 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 deben pasar.
Si siguió los pasos de la sección de autenticación básica, tendrá que agregar lo siguiente al bloque de instalación para hacer que todas las pruebas pasen:
request.headers['Authorization'] = ActionController::HttpAuthentication::Basic.
encode_credentials('dhh', 'secret')
Si está familiarizado con el protocolo HTTP, sabrá que get es un tipo de solicitud. Hay 6 tipos de solicitud soportados en las pruebas funcionales de Rails:
get
post
patch
put
head
delete
Todos los tipos de solicitud tienen métodos equivalentes que puede utilizar. En una típica aplicación C.R.U.D. va a utilizar get
, post
, put
y delete
más a menudo.
Las pruebas funcionales no verifican si el tipo de petición especificado es aceptado por la acción, estamos más preocupados aqui por el resultado. Las pruebas de solicitud existen para este caso de uso con el fin de hacer que sus pruebas sean más útiles.
Para probar las solicitudes de AJAX, puede especificar la opción xhr: true
para los metodos get
, post
, 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.content_type
end
Después de que se haya realizado y procesado una solicitud, tendrá 3 objetos Hash listos para su uso:
- cookies - Cualquier cookie que se establezca
- flash - Cualquier objeto que vive en el flash
- session - Cualquier objeto que vive en 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 nombre de símbolo. Por ejemplo:
flash["gordon"] flash[:gordon]
session["shmession"] session[:shmession]
cookies["are_good_for_u"] cookies[:are_good_for_u]
También tiene acceso a tres variables de instancia en sus pruebas funcionales, después de hacer una solicitud:
@controller
- El controlador que procesa la solicitud@request
- El objeto request@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
Los encabezados HTTP y las variables CGI se pueden pasar como encabezados:
# setting an HTTP Header
get articles_url, headers: { "Content-Type": "text/plain" } # simulate the request with custom header
# setting a CGI variable
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simulate the request with custom env variable
Si recuerdas uno de los puntos anteriores, uno de los Tres Hashes del Apocalipsis fue flash
.
Queremos agregar un mensaje flash
a nuestra aplicación de blog cada vez que alguien crea con éxito un nuevo artículo.
Comencemos añadiendo este assert a nuestra prueba test_should_create_article
:
test "should create article" do
assert_difference('Article.count') do
post article_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
Vamos a implementar el mensaje flash
ahora en nuestro controlador. Nuestra acción :create
ahora debe 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 verlo pasar:
$ 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
En este punto, nuestro controlador de artículos testea las acciones :index
, así como :new
y :create
. ¿Qué pasa con los 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
Recuerde nuestra discusión anterior en los fixtures, donde el método de los articles()
nos dará acceso a nuestros fixtures de los articles.
¿Y qué hay sobre 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 añadir 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)
# Reload association to fetch updated data and assert that title is updated.
article.reload
assert_equal "updated", article.title
end
Tenga en cuenta que estamos empezando a ver alguna duplicación en estas tres pruebas, ambos tienen acceso a los mismos datos del fixture del artículo. Podemos usar la filosofía D.R.Y. Esto mediante el uso de los métodos de configuración y desmontaje proporcionados por ActiveSupport::Callbacks
.
Nuestra prueba ahora debe ser algo como lo que sigue. Ignore las otras pruebas por ahora, las dejamos por brevedad.
require 'test_helper'
class ArticlesControllerTest < ActionDispatch::IntegrationTest
# called before every single test
setup do
@article = articles(:one)
end
# called after every single test
teardown do
# when controller is using cache it may be a good idea to reset it afterwards
Rails.cache.clear
end
test "should show article" do
# Reuse the @article instance variable from 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)
# Reload association to fetch updated data and assert that title is updated.
@article.reload
assert_equal "updated", @article.title
end
end
Similar a otras devoluciones de llamada en Rails, los métodos de configuración y desmontaje también se pueden usar pasando un nombre de bloque, lambda o método como un símbolo para llamar.
Para evitar la duplicación de código, puede agregar sus propios ayudantes de prueba. El ayudante sign 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
# helper is now reusable from any controller test case
sign_in_as users(:david)
get profile_url
assert_response :success
end
end