Mocha, expectativas en instancias

Hola,

he peleado, mirado los docs y solucionado una cosa con Mocha, pero no
estoy
seguro de que sea la mejor forma.

Estoy testeando un fragmente de código como este

— identity.rb
def generate_connections

if new_connections.any?
new_connections.each do |conn|
# Notification for the new connections
recipient = User.find(conn[:user_id])
logger.info(“Notificación de conexion para #{recipient.login}”)
recipient.send_message(:new_connection, {:id => self.user_id,
:login
=> self.login, :service => conn[:service] } )
end
# Notification for the user
recipient = self.user
logger.info(“Notificación de conexion para #{recipient.login}”)
recipient.send_message(:found_connections, { :connections =>
new_connections } )
end

end

Y quiero probar que se invoca el send_message para cada instancia de
user (2
veces). Finalmente lo he solucionado, aparentemente, con

context ‘generating connections’ do
setup {
@mort = Factory(:user)
@calvin = Factory(:calvin)
@demimismo = Factory(:demimismo)

  ....

  User.any_instance.expects(:send_message).times(2)

  @mort_identity.generate_connections
  @calvin_identity.generate_connections
}

Como digo, no estoy nada seguro de mi solución. Se trata, en general, de
cómo asignar expectativas a variables locales como ‘recipient’ en este
ejemplo.

Tengo la sensación de haberme explicado un tanto farragosamente, pero
ahora
mismo creo que no me sale nada mejor :slight_smile:

Creo que he entendido la pregunta, aunque yo tampoco ando muy fino hoy
:stuck_out_tongue:

El único riesgo que corres de esta forma es que las instancias de User
que reciban send_message no sean las que tú querías. Yo uso este mismo
método cuando el código no es muy enrevesado ni tiene llamadas a
código que pueda interferir con los expects.

Cuando quiero asegurarme de que las instancias son efectivamente las
que yo quiero o verificar qué llamada concreta se hace a qué
instancia, simulo mediante un mock o un stub la llamada a la
funciónque las genera y a partir de ahí ya puedo fijar las expectations en
cada una de ellas.

En tu código diría que no hace falta, pero la cosa sería algo en plan:

las vbles locales a testear son los recipientes obtenidos en

User.find:
User.expects(:find).times(3).returns rec1, rec2, rec3

ahora podemos comprobar qué le ocurre a cada uno

rec1.expects…

Releyendo mi mensaje igual tampoco se entiende bien, si quieres
jugamos con un ejemplo completo para que se vea más claro :wink:

2008/8/28 Manuel González Noriega [email protected]:

if new_connections.any?
recipient.send_message(:found_connections, { :connections =>
setup {
}

Como digo, no estoy nada seguro de mi solución. Se trata, en general, de
cómo asignar expectativas a variables locales como ‘recipient’ en este
ejemplo.

Tengo la sensación de haberme explicado un tanto farragosamente, pero ahora
mismo creo que no me sale nada mejor :slight_smile:

Deberías hacer que recipient fuera uno de tus mocks, para poder
implementar expectativas dentro de ellos, pero creo que te has chocado
con el problema de los mocks, que cuando empiezas parece que no puedes
acabar:

Configura @recipient_mock_1 y @recipient_mock_2 para hacer de

recipients

Creo que para tí podrían ser @mort y @calvin, pero no estoy seguro

@mort.expects(:send_message)…

User.stubs(:find).returns(@recipient_mock_1, @recipient_mock_2)

Lo que no veo es de donde sale new_connections, pero en principio no
tendría que molestar a menos de que no tuviese únicamente dos
elementos.

Hay otra cosa que me gustaría hacer notar: parece que invocas dos
veces a generate_connections en el mismo test. Supongo que @calvin y
@mort son diferentes en algún aspecto (vienen de diferentes factorias)
y no parece que generate_connections tenga “efectos laterales”. Si ese
es el caso y estás comprobando el funcionamiento dependiendo del
aspecto que les diferencia ¿no deberían ser dos test diferentes?

Suerte.

2008/8/28 Daniel R. Troitiño [email protected]

2008/8/28 Manuel González Noriega [email protected]:

Hay otra cosa que me gustaría hacer notar: parece que invocas dos
veces a generate_connections en el mismo test. Supongo que @calvin y
@mort son diferentes en algún aspecto (vienen de diferentes factorias)
y no parece que generate_connections tenga “efectos laterales”. Si ese
es el caso y estás comprobando el funcionamiento dependiendo del
aspecto que les diferencia ¿no deberían ser dos test diferentes?

Muchas gracias a Raul y a ti, voy a ver si con los hints que me dais y
la
doc de Shoulda logro hacer y sobre todo entender.

En realidad, uno de los dos generate_connections sobra. El
generate_connections crea conexiones entre usuarios de forma
automatizada en
función de unas determinadas condiciones (básicamente, que al importar
sus
datos de otras redes sociales determinemos que se “conocen”), por eso
necesito instanciar crear al menos dos usuarios mediante factory_girl.

Pues eso, voy a leer con calma vuestras sugerencias, hacer una prueba y
probablemente volver lloriqueando dentro de un rato :slight_smile:

Una cosa que olvidaba comentar: en mi caso me he dado cuenta de por lo
general la necesidad de testear variables locales es un “bad smell”
que indica que debería revisar un poco ese fragmento de código.

El que tus propios tests te indiquen cómo mejorar tu código es otro de
esos detalles que te animan a seguir testeando :smiley:

2008/8/28 Raul M. [email protected]

Una cosa que olvidaba comentar: en mi caso me he dado cuenta de por lo
general la necesidad de testear variables locales es un “bad smell”
que indica que debería revisar un poco ese fragmento de código.

El que tus propios tests te indiquen cómo mejorar tu código es otro de
esos detalles que te animan a seguir testeando :smiley:

Mmm, sí, lo que estoy haciendo es enviar notificaciones a cada usuario,
después de crear una conexión. El sitio intuitivo para colocar esto
parece
un after_create de Connection o en un ConnectionObserver, pero al
usuario
que provoca la conexión no le deben llegar N mensajes para N conexiones,
sino 1 mensaje que resuma las N conexiones. Así que al ser un batch se
descarta el after_create. Sí sería posible mandar desde el after_create
la
notificación al usuario ‘receptor’, pero ya puestos prefiero hacerlo en
el
mismo sitio.

¿Mucho lío? :slight_smile:

El día 28 de agosto de 2008 17:59, Manuel González Noriega
[email protected]
escribió:> Mmm, sí, lo que estoy haciendo es enviar notificaciones a cada usuario,

después de crear una conexión. El sitio intuitivo para colocar esto parece
un after_create de Connection o en un ConnectionObserver, pero al usuario
que provoca la conexión no le deben llegar N mensajes para N conexiones,
sino 1 mensaje que resuma las N conexiones. Así que al ser un batch se
descarta el after_create. Sí sería posible mandar desde el after_create la
notificación al usuario ‘receptor’, pero ya puestos prefiero hacerlo en el
mismo sitio.

¿Mucho lío? :slight_smile:

Jajaja, no, pero sin ver el tema más de cerca es muy posible que meta
la pata. Yo me refería al típico divide y vencerás, aligerando el
método generate_connections (por los “…” que incluye intuyo que no
es pequeño) y delegando el envío de las notificaciones a otro (digamos
notify_connections), que reciba las nuevas conexiones. Creo que es
este segundo método el que queríamos testear en el ejemplo.

Ahora debería ser más fácil testear notify_connections, porque le
puedes pasar como entrada unas conexiones fabricadas a manopla
(supongo que el usuario “receptor” ya lo podías fijar en el código
original).

2008/8/28 Manuel González Noriega [email protected]

Muchas gracias a Raul y a ti, voy a ver si con los hints que me dais y la
doc de Shoulda logro hacer y sobre todo entender.

Pues eso, voy a leer con calma vuestras sugerencias, hacer una prueba y
probablemente volver lloriqueando dentro de un rato :slight_smile:

Bien,

primero, he seguido la acertadísima sugerencia de Raul y he
refactorizado a

def generate_connections

if new_connections.any?
notify_connections(new_connections)
notify_user(new_connections)
end

end

Con lo cuál lo referido a esa parte del test pasa a ser un conciso

@mort_identity.expects(:notify_connections)
@mort_identity.expects(:notify_user)
@mort_identity.generate_connections


Pero aunque con esto funciona, tengo problemas (graves) para ver la
relación
entre el returns y el with de la expectativa y las variables usadas en
el
código de los métodos.

Es decir, si mi código tiene (véase snippet anterior)


recipient = User.find(conn[:user_id])
recipient.send_message(:new_connection, {:id => self.user_id, :login =>
self.login, :service => conn[:service] } )

  • “with” ¿qué debe ser exactamente? En el código es un entero en un
    array.
    ¿Cómo describo eso con with? Los ejemplos que encuentro pasan a with
    cosas
    como enteros, cadenas o símbolos. ¿A qué se corresponden los símbolos?
    ¿A
    variables de instancia con el mismo nombre en el código?

  • “returns”, si es un mock de un find ¿debe ser una instancia del
    modelo,
    proviniente de una Factory o una fixture?

Soy un Mar de los Sargazos de dudas, pero sospecho que probablemente se
deban a mirar la doc con legañas. Me aplicaré más :slight_smile:

2008/8/28 Raul M. [email protected]

Y no me enrollo más, perdonad la chapa :stuck_out_tongue:

Muchas gracias por todo el tiempo invertido en la excelente explicación
:slight_smile:
Seguiré adelante con los tests y la próxima vez que llegue a un
escenario
como este comprobaré si algo ha calado en mi encallecido cerebro.

El día 28 de agosto de 2008 20:11, Manuel González Noriega
[email protected]
escribió:> ¿Cómo describo eso con with? Los ejemplos que encuentro pasan a with cosas

como enteros, cadenas o símbolos. ¿A qué se corresponden los símbolos? ¿A
variables de instancia con el mismo nombre en el código?

Por lo que yo sé, el with son los argumentos exactos que esperas en la
llamada. En la doc del with usan símbolos porque realmente esperan
símbolos:
http://mocha.rubyforge.org/classes/Mocha/Expectation.html#M000042

  • “returns”, si es un mock de un find ¿debe ser una instancia del modelo,
    proviniente de una Factory o una fixture?

Así es: si es un mock de un find, returns debe devolver un objeto
ActiveRecord (que puede provenir de cualquiera de esas fuentes que
comentas). Si quieres comprobar que find va a recibir como parámetro
@mort.id y te interesa devolver @mort para seguir adelante con el
test, la cosa sería:

User.expects(:find).with(@mort.id).returns @mort

Si te interesa puedes aprovechar el “ducktyping”: puedes devolver un
objeto que responda a todos aquellos métodos que vas a aplicar sobre
él (uséase: que puede ser un mock o un stub si quieres simular sus
métodos). El ejemplo típico es una llamada a un webservice:

@photo = stub(:some_photo, :size => ‘640x480’, :url => ‘http://…’)
SomeAPI.expects(:get_photo).returns @photo

y aquí el código a testear, en el que @photo tiene disponibles size y url

De esta forma, además de comprobar que SomeAPI espera que llames a
get_photo estás devolviendo un objeto que puede seguir usándose en el
código a testear, todo ello sin necesidad de llamar al webservice
real.

He creado @photo como un stub por comodidad, pero si nos interesara
saber si alguien va a invocar por ejemplo al método size podríamos
usar:

@photo = stub(:some_photo, :url => ‘http://…’)
@photo.expects(:size).returns ‘640x480’

Esto se debe a que el constructor stub en realidad crea un mock, y
“stubbea” los métodos que recibe en los parámetros, por lo que es muy
cómodo para simularlos si no nos interesa crear expectativas con
ellos.

Y no me enrollo más, perdonad la chapa :stuck_out_tongue: