Tutorial de Mock y Stub para Ruby o para Rails

Llevo buscando un buen tiempo algun tutorial o manual que explique bien
qué es un Stub y un Mock y cómo aplicarlos pero no encuentro algo que me
despeje las dudas. He reescrito desde cero el fichero que testea mi
controlador Clubs y me ha quedado así:

describe ClubsController do
describe “Petición GET /index” do
it “debe ser exitosa” do
get ‘index’
response.should be_success
end

    it "debe buscar todos los clubes" do
        Club.should_receive(:find).with(:all).and_return(@clubs)
        get 'index'
    end

    it "debe renderizar la plantilla index" do
        get 'index'
        response.should render_template('index')
    end

    it "debe asignar a la vista los clubes encontrados" do
        #aquí no sé que poner :S
    end
end

end

Como véis es la acción index por defecto que te genera un scaffold de
Rails y no sé cómo puedo testear que lo que se muestra en la vista es lo
mismo que he cargado en el controlador, he probado poniendo esto:

it “debe asignar a la vista los clubes encontrados” do
assigns[:clubs].should == Club.find(:all)
get ‘index’
end

pero me lanza el sigueinte error:

NoMethodError in ‘ClubsController Petición GET /index debe asignar a la
vista los clubes encontrados’
You have a nil object when you didn’t expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.include?
/home/carlos/NetBeansProjects/ofs/spec/controllers/clubs_controller_spec.rb:22:

¿Alguien me echa una mano?

Hola,

it “debe asignar a la vista los clubes encontrados” do
assigns[:clubs].should == Club.find(:all)
get ‘index’
end

Como estás probando el controlador, y lo que quieres es comprobar que,
al ejecutarse, asigna el resultado de “Club.find(:all)” a @clubs, has de
hacerlo al revés:

it “debe asignar a la vista los clubes encontrados” do
get ‘index’
assigns[:clubs].should == Club.find(:all)
end

Primero lo ejecutas, luego compruebas que, en efecto, assigns[:clubs]
tiene lo que debería.

De todas formas, lo que estás haciendo es probar contra la base de datos
con Club.find(:all). Lo suyo es testear en isolation, y para eso puedes
usar mocking y stubbing. Yo haría primero lo siguiente:

describe ClubsController do
before(:each) do
@club = mock_model(Club)
@clubs = [@club]

Club.stub!(:find).and_return(@clubs)

end

Con eso te aseguras que, antes de cada spec, se hace un stub de
Club.find que devuelve un array con un club de prueba (recuerda que aquí
no estás probando los clubs, solo el controlador, así que lo que hacen
los clubs te debería dar igual). Así, cuando haces get :index, usará lo
que has definido en el before block y no lo que hay en la base de datos.

Luego en:
it “debe buscar todos los clubes” do
Club.should_receive(:find).with(:all).and_return(@clubs)
get ‘index’
end

Aquí lo que le estás diciendo es: “olvídate del stub que creé. lo que
quiero es que ahora compruebes que en efecto, Club recibe (:find) con
(:all) y devuelve (@clubs)”. Y la última parte (and_return), en realidad
es como decirle “no ejecutes la llamada, solo devuelve mi instance
variable @clubs”.

Y por último:

    it "debe asignar a la vista los clubes encontrados" do
         get 'index'
         assigns[:clubs].should == @clubs
    end

Aquí, como tienes un stub de :find, que devuelve @clubs, al ejecutar
index va a usar tu @clubs para asignarlo a la vista. Solo tienes que
comprobar que en efecto es así.

No he probado el código, pero en conjunto tendría esta pinta:

http://www.pastie.org/218663

Saludos,
Jorge

Carlos Belizón wrote:

Llevo buscando un buen tiempo algun tutorial o manual que explique bien
qué es un Stub y un Mock y cómo aplicarlos pero no encuentro algo que me
despeje las dudas. He reescrito desde cero el fichero que testea mi
controlador Clubs y me ha quedado así:

describe ClubsController do
describe “Petición GET /index” do
it “debe ser exitosa” do
get ‘index’
response.should be_success
end

    it "debe buscar todos los clubes" do
        Club.should_receive(:find).with(:all).and_return(@clubs)
        get 'index'
    end

    it "debe renderizar la plantilla index" do
        get 'index'
        response.should render_template('index')
    end

    it "debe asignar a la vista los clubes encontrados" do
        #aquí no sé que poner :S
    end
end

end

Como véis es la acción index por defecto que te genera un scaffold de
Rails y no sé cómo puedo testear que lo que se muestra en la vista es lo
mismo que he cargado en el controlador, he probado poniendo esto:

it “debe asignar a la vista los clubes encontrados” do
assigns[:clubs].should == Club.find(:all)
get ‘index’
end

pero me lanza el sigueinte error:

NoMethodError in ‘ClubsController Petición GET /index debe asignar a la
vista los clubes encontrados’
You have a nil object when you didn’t expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.include?
/home/carlos/NetBeansProjects/ofs/spec/controllers/clubs_controller_spec.rb:22:

¿Alguien me echa una mano?

Jorge Gomez Sancha wrote:

Hola,

Sí, eso es exactamente (o muy parecido) a lo que hace el scaffold de
rspec, pero es que sigo sin ver qué es un mock y un stub, es decir ¿Qué
hace cada uno?

Hola Carlos, hace buen tiempo que no toco RSpec y tampoco soy un experto
en
esto, pero tratare de explicarte algo en cuanto a lo que yo entiendo:

Tanto el mock como el stub son ‘test doubles’ osea copias de otros
objetos
pero sin implementacion, algo asi como si tuvieras una clase vacia y
crearas
una instancia, ahora no solo se pueden crear stubs de una clase sino
tambien
puede de ser de un metodo(ya que ambos son un objeto desde el punto de
vista
de Ruby), a esto si mal no recuerdo en RSpec le llaman ‘partial stubs’,
osea
que tienes un objeto original pero con algunos de sus metodos
reemplazados
para dar una ‘respuesta enlatada’.

Ahora lo que diferencia a un stub de un mock es la intencion(no se de
que
otra manera llamarlo) que tu tienes para esa spec, por ejemplo

before(:each) do
@club = mock_model(Club)
@clubs = [@club]
Club.stub!(:find).and_return(@clubs)
end

Arriba tenemos un bloque de codigo que sera ejecutado antes de cada
spec,
entonces si tenemos la siguiente spec:

it “debe ser exitosa” do
get ‘index’
response.should be_success
end

Lo que aqui nos interesa es que la respuesta enviada por el servidor sea
satisfactoria, osea que tenga el codigo 200, pero no nos interesa saber
si
se obtienen o no un listado de clubs, es por eso que tienes un stub en
ese
bloque de codigo, ahora miremos la siguiente spec:

it “debe buscar todos los clubes” do
Club.should_receive(:find).with(:all).and_return(@clubs)
get ‘index’
end

En la spec de arriba nuestra intencion es comprobar que se busquen y
devuelvan un conjunto de clubs, es por eso que debemos ser mas
especificos
en cuando a la llamada al metodo find, y esa creo que es la principal
diferencia entre un mock y un stub, el mock te permite ir mas a fondo en
la
comunicacion que hay con el objeto, mientras que un stub simplemente te
da
una respuesta inmediata no puedes especificar nada mas.

Espero que te haya quedado algo mas claro, comprendo tu duda ya que a mi
me
costo algo mas de 1 mes comprender esta diferencia, y para sazonar esto
te
dejo un enlace:

Saludos.

El día 20 de junio de 2008 6:27, Carlos Belizón <
[email protected]> escribió:

Comenta la linea que dices y veras que fallara esta spec:

it “debe asignar a la vista los clubes encontrados” do
get ‘index’
assigns[:clubs].should == @clubs
end

expected: [#<Club:0x…fdb7ccdf0 @name=“Club_1028”>],
got: [] (using ==)

como la llamada al metodo de clase find del objeto club nunca llega
realizarse entonces obtendremos un array vacio.

Saludos.

El día 20 de junio de 2008 14:44, Carlos Belizón <
[email protected]> escribió:

Ruben Davila wrote:

Comenta la linea que dices y veras que fallara esta spec:

it “debe asignar a la vista los clubes encontrados” do
get ‘index’
assigns[:clubs].should == @clubs
end

expected: [#<Club:0x…fdb7ccdf0 @name=“Club_1028”>],
got: [] (using ==)

como la llamada al metodo de clase find del objeto club nunca llega
realizarse entonces obtendremos un array vacio.

Saludos.

El día 20 de junio de 2008 14:44, Carlos Belizón <
[email protected]> escribió:

Es decir, ¿En esa línea estoy creando un “falso método” find que
devuelve un vector de clubes?

Ruben Davila wrote:

before(:each) do
@club = mock_model(Club) #esta orden entiendo que crea una instancia de una clase “falsa”
@clubs = [@club] #aquí la usamos como si fuera una instancia real
Club.stub!(:find).and_return(@clubs) # ¿Y aquí que estamos haciendo?
end

Esa línea no entiendo bien por qué ha de existir para que se llame antes
de cada caso de uso, si eres tan amable de explicarmelo creo que ya
entenderé finalmente a diferenciarlos bien.

Muchas gracias, porque con rSpec me sentía bastante perdido.

Carlos Belizón wrote:

Es decir, ¿En esa línea estoy creando un “falso método” find que
devuelve un vector de clubes?

¿Para ser mas específico, en esa línea estoy creando ese “falso método”
find, o lo estoy creando a la vez que lo uso?

El día 20 de junio de 2008 22:13, Ruben. D. [email protected]
escribió:> expected: [#<Club:0x…fdb7ccdf0 @name=“Club_1028”>],

 got: [] (using ==)

como la llamada al metodo de clase find del objeto club nunca llega
realizarse entonces obtendremos un array vacio.

Rubén, perdona si me equivoco (en su día cacharreé un poco con rspec)
pero creo que la llamada a Club.find sí se realiza desde el
controlador: lo que ocurre es que al no existir el stub la llamada no
se simula: se realiza sobre los datos reales y como en la base de
datos no existe ningún club el resultado es un array vacío.

eco Raul, tienes toda la razón, a proposito ya que cai aqui, estoy
probando
esto que se ve guapo:

http://www.lindenlan.net/2008/03/03/rspec-autotest-and-ruby-libnotify/

Saludos.

El día 20 de junio de 2008 15:36, Raul M. [email protected]
escribió: