El enfoque correcto con TDD en Rails te permite construir endpoints confiables desde el primer día. Aquí verás cómo diseñar pruebas con RSpec y generar datos con FactoryBot y Faker para los GET /posts y GET /posts/:id, validando el payload, el estado 200 y el comportamiento con y sin datos. La clave: escribir primero las pruebas, provocar los fallos esperados y luego implementar.
¿Cómo aplicar TDD para los endpoints de posts en Rails?
Es vital partir desde la carpeta spec y crear un nuevo spec para posts. Se plantean dos rutas: listar con GET /posts y mostrar con GET /posts/:id. Primero se copian y adaptan estructuras previas, luego se define qué debe ocurrir en cada caso: cuando no hay datos la lista debe estar vacía y el estado debe ser 200. Con datos insertados, el payload debe igualar en tamaño a lo creado y también responder 200.
¿Qué pruebas cubren listar y mostrar posts?
Listar sin datos: lista vacía y estado 200.
Listar con datos: tamaño del payload igual a los posts creados y estado 200.
Mostrar detalle: payload no vacío, estado 200 y el ID del payload igual al del post creado.
Ejemplo de estructura en RSpec para index y show:
# spec/requests/posts_spec.rbrequire'rails_helper'RSpec.describe 'Posts',type::requestdo describe 'GET /posts'do context 'sin datos en la base de datos'do it 'retorna lista vacía y 200'do get '/posts' expect(response.status).to eq(200) expect(JSON.parse(response.body)).to be_empty
endend context 'con datos en la base de datos'do let(:posts){ FactoryBot.create_list(:post,10,published:true)} it 'retorna todos los posts publicados y 200'do posts
get '/posts' payload =JSON.parse(response.body) expect(response.status).to eq(200) expect(payload.size).to eq(posts.size)endendend describe 'GET /posts/:id'do it 'retorna un post y 200'do post = FactoryBot.create(:post) get "/posts/#{post.id}" payload =JSON.parse(response.body) expect(response.status).to eq(200) expect(payload['id']).to eq(post.id)endendend
¿Cómo estructurar RSpec con describe, context y let?
Usa describe para agrupar por ruta o acción.
Separa escenarios con context: "con datos" y "sin datos".
Declara datos con let para crear listas con FactoryBot. Esto los hace reutilizables dentro de cada prueba.
¿Cómo generar datos de prueba con FactoryBot y Faker?
Primero, genera los factories con el generador de Rails: rails g factory bot model user email:string name:string auth_token:string y luego para post con title:string content:string published:boolean user:references. Después, ajusta los valores por defecto usando Faker para que los datos sean válidos y realistas.
¿Cómo crear factories para user y post?
# spec/factories/users.rbFactoryBot.define do factory :userdo email { Faker::Internet.email } name { Faker::Name.name } auth_token {'my_string'}endend
# spec/factories/posts.rbFactoryBot.define do factory :postdo title { Faker::Lorem.sentence } content { Faker::Lorem.paragraph } published { r = rand(0..1) r ==0?false:true} user
endend
Puntos clave al crear factories:
Usa Faker para emails, nombres, títulos y párrafos.
Define published como booleano real, no nulo.
Declara la asociación con user para que se cree automáticamente.
¿Cómo usar datos aleatorios y asociaciones?
Datos aleatorios ayudan a detectar fallos por validaciones reales.
La asociación user en el factory de post evita tener que crear usuarios manualmente en cada prueba.
¿Cómo validar booleanos y solucionar errores comunes?
Al ejecutar las pruebas y la consola en el entorno de pruebas con la variable de entorno de Rails, se detectó un error típico: el validador de presencia marca false como vacío en atributos booleanos. Por eso, aunque published estaba en false, se consideraba inválido.
¿Por qué presence falla con booleanos?
El validador de presence trata false como valor vacío.
Con booleanos, false puede ser un estado válido y debe aceptarse.
¿Qué cambio hacer con inclusion para published?
Reemplaza la presencia por inclusión explícita en booleanos:
Además, ajusta la prueba del modelo para no requerir presencia en published. Tras este cambio, las fallas se reducen a lo esperado por TDD: rutas inexistentes aún por implementar en el controlador.
¿Quieres que revisemos la implementación del controlador y las rutas para que las pruebas pasen? Deja tus preguntas y comenta qué parte te interesa profundizar.
Implementación de Endpoints para Listar y Mostrar Posts con TDD
Realmente para mi ha sido difícil seguir el curso porque no siento que haya una explicación clara de porqué se debe hacer cada cosa, si uno está empezando en este mundo hay momentos en los que se siente perdido y/o estancado en el tema.
Es mi opinión, tal vez solo me pase a mi.
No solo a ti. Aún cuando tengo una base de Ruby y Rails, me ha costado seguirle la pista las clases porque no sé cuál es la estrategia que está pensando el profesor. En este curso ha sido más clara al usar diagramas con el iPad, pero es fácil perderse entendiendo lo que está pensando el profesor.
Yo entiendo un poquito más porque ya antes tomé el curso de Ruby on Rails aquí en Platzi. Te recomiendo que lo sigas tomando este (aunque no entiendas nada), luego vas y tomas aquél, y luego regresas a este, y luego regresas a aquél, jajaja Suena enredoso pero solo así logras consolidar bien los conceptos.
Yo creo que en el FactoryBot de post podrias haber usado:
[false, true].sample
en vez de el if que hiciste, es mas legible y menos redundante.
Para evitar el problema con los posts que tienen user_id en nil en FactoryBot 5, solo se tiene que agregar FactoryBot.use_parent_strategy = false
al principio del archivo de posts.rb
Para quien instaló las gemas sin el versionamiento (like me), por alguna razón el FactoryBot de última versión al hacer FactoryBot.build(:post) no genera el USER por lo que queda el post con un user_id: nil y se mantiene en valid? FALSE.
Recomiendo instalar las versiones que el profesor recomienda. O ahondar en la documentación con las versiones finales.
Me acaba de pasar lo mismo. Gracias por tu aporte!
Cuando estamos en el ambiente de test, se podría utilizar el comando “reload!” en la consola de Rails, para refrescarla y evitar salir y volver a ejecutarla?
It usually works for me in development enviroment. But it didn't work for me here.
@diegoalejojo, tks for the info.
¿Pero ese token que el proveedor le retorna a la SPA, también se lo tiene que hacer saber a la App no es así? De otra forma... ¿el backend cómo se va a enterar que ese token que le están enviando es valido?
Cuando se genera un token, el proveedor (Auth0) firma lo firma con una llave privada y una llave pública. La llave privada sólamente la tiene Auth0 y debe garantizar que está bien custodiada para que podamos confiar en él y la llave pública, como su nombre lo dice, es pública y cualquiera puede verla o descargarla. Esas "llaves" son literalmente un par de strings que son generados usando unos principios matemáticos que hacen que estén "conectadas". De esta manera el que firma puede enviar mensajes firmados y cualquier persona o servicio puede verificar que el mensaje fue firmado por Auth0 usando la llave pública.
Algo así como que tu dedo es la llave privada y la foto de tu huella es la llave pública. Si tu envías una carta, pones tu huella en la carta y publícas una foto de tu huella, cualquiera podría verificar que esa carta es tuya pero nadie podría falsificar tus cartas porque nadie más tiene tu dedo para poner una huella.
En el código del proyecto, en la clase JsonWebToken puedes ver que descargamos la llave pública de para verificar el token:
classJsonWebToken def self.verify(token)JWT.decode(token, nil,true, # Verify the signature ofthis token
algorithm:'RS256',iss:'https://simon0191.auth0.com/',verify_iss:true,aud:'MH2k1IeuaRhWBbm6kq5wV9g1n7w7Bvfa',verify_aud:true)do|header| jwks_hash[header['kid']] end
end
def self.jwks_hash jwks_raw =Net::HTTP.getURI("https://simon0191.auth0.com/.well-known/jwks.json") jwks_keys =Array(JSON.parse(jwks_raw)['keys'])Hash[ jwks_keys
.mapdo|k|[ k['kid'],OpenSSL::X509::Certificate.new(Base64.decode64(k['x5c'].first)).public_key] end
] end
end
De esta manera garantizamos que solamente vamos a recibir tokens de Auth0, puesto que si recibimos un token que no haya sido firmado por Auht0, la validación con la llave pública va a fallar.
No entiendo porque el user no tiene un controller con o metodo create, y al profe si le crea el user_id asociado al post en la consola.
Para las personas que están intentando correr el comando:
FactoryBot.build(:post)
y les da el siguiente error:
gems/i18n-1.12.0/lib/i18n.rb:210:in `translate’: wrong number ofarguments(given 2, expected 0…1)(ArgumentError)
Es por la versión de Faker que ya es antigua y no es compatible con las versiones recientes de Rails, para actualizar Faker a la versión más reciente, solamente borren la versión de Faker en su Gemfile, quedando de la siguiente forma:
después eliminen el archivo Gemfile.lock de su carpeta raíz del proyecto y vuelvan a correr el comando:
bundle install
Con esto ya les debería funcionar el FactoryBot.build(:post)
No me funciono
Retiro lo dicho funciono correctamente.
A la hora de correr factory en el ambiente de pruebas de la consola rails, para crear post, con el comando FactoryBot.build(:post) solo crea el post, sin el user id, para que corra correctamente utilice el comando FactoryBot,create :post ,creando correctamente el post y el user id, creo que se debe a las actualizaciones que han habido de la fecha de la grabacion del curso a la actual
Hola! Sobre el refactor que se hizo a la validación de published, encontré una mejor idea para testear esto, en vez de borrar la linea. Se puede usar el método validate_inclusion_of. Más info aquí
RSpec.describePost,type::model do describe 'validations'do it 'validates presence of required fields'do should validate_presence_of(:title) should validate_presence_of(:content) should validate_inclusion_of(:published).in_array([true,false]) should validate_presence_of(:user_id) end
end
end
Saludos. Las pruebas me están arrojando error en el método create. Alguien sabe qué podría ser?
Gracias.
3)PostGET/post/{id} should return a post
Failure/Error:let(:post){create(:post)}NoMethodError:undefined method `create' for #<RSpec::ExampleGroups::Post_2::GETPostId "should return a post" (./spec/requests/posts_spec.rb:29)>
./spec/requests/posts_spec.rb:27:in `block(3 levels)in<main>'
./spec/requests/posts_spec.rb:30:in `block(3 levels)in<main>'
Buenas tardes, una pregunta: la variable response de 'response.body' viene por defecto on rspec o es de otra gema?
Hola,
a diferencia de node.js con express, donde se necesita "body-parser", en Rails ( y en Django también) viene con el parser y dispatcher incluidos ó para importar desde el mismo framework, Rails utiliza: Response de ActionDispatch, el cuál tiene body.
Esto sucede porque es un "opinionated Frameworks", que tiene una estructura pre-definida y no deberia cambiarse, seguro que si se pueden reemplazar partes, pero por defecto ya vienen con todo cargado :)
Me sale esto:
FactoryBot.build(:post)
/root/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/gems/i18n-1.12.0/lib/i18n.rb:210:in `translate': wrong number of arguments (given 2, expected 0..1) (ArgumentError)
El mismo error tengo, igual ando tratando de solucionarlo
El error es por la versión de Faker que es incompatible con las versiones de Rails más recientes, seguramente tienes una versión de Rails entre 6 y 7, por lo tanto, la versión de Faker que usan en este curso por el maestro es la 1.9 (ya vieja).
Quítale la versión al Faker en tu Gemfile, borra el archivo Gemfile.lock que está en la carpeta raíz de tu proyecto y vuelve a hacer bundle install para que se actualice a la versión más reciente de Faker, con eso ya te funcionará FactoryBot.build(:post)
Hasta aquí llego, me he frustrado a tope. Además de ser un curso antiguo, siento que la explicación no es clara (pese a que llevé los cursos previos recomendados por la ruta). De tanto pausar y buscar mejores explicaciones, estoy aprendiendo más incluso con Youtube, contenido totalmente gratuito y de calidad. Uno, al pagar por Platzi, espera recibir lo mejor, pero no es el caso.
Hola Cath. Que recursos encontraste en Youtube que puedas compartir? Gracias de antemano !
Hola Leon, puedes complementar tus estudios con este curso de Rails 7 en español:
tuve un error usando la version 5.0 de factorybot me salia KeyError (Factory not registered: "post")
asi que volvi a la version 4.0
Entonces como haces el test con RSpec de los boolean.
Podemos hacerlo con el matcher allow_value asi:
should allow_value(true, false).for(:published)
Cómo hizo para hacer pasar el test de la lista de posts???
Probe con factorybot desde la consola crear 10 posts, pero cuando vuelvo a ejecutar rspec obtengo este error