Carlos Alejandro Hernández Mejía
Ricardo Bladimir Estupinián López
Jasmine spyOn
La separación de responsabilidades es una de las buenas prácticas que encontramos en un proyecto de software. Separar responsabilidades significa agrupar código de tal manera que cada conjunto se encargue de una tarea específica.
Hablemos ahora de las pruebas unitarias. Cuando probamos un componente, queremos probar las responsabilidades que le estamos delegando al componente y no todo el código ejecutado. Es decir, si el componente A utiliza la función b(), lo que queremos probar es la lógica propia del componente y no el código de la función.
Veamos el siguiente ejemplo:
function GetMonthApi() { this.currentMonth = function () { return 'May'; } this.nextMonth = function () { return 'June' } } export function MonthCalculator() { this.api = new getMonthApi(); this.getNextMonth = function() { return this.api.nextMonth(); } this.getCurrentMonth = function() { return this.api.currentMonth(); } }
La clase MonthCalculator nos permite calcular el valor del mes actual y el siguiente. Cómo puedes ver, la función getMonthApi es utilizada dentro de la clase. Si ejecutas el set de pruebas asociadas a nuestra clase se ejecuta en Mayo el valor del siguiente mes sería Junio, o si las corres en Enero el siguiente mes sería Febrero. ¿Ves el problema?.
Si ejecutamos código que se encuentre fuera del domino de un componente, nos podemos encontrar con resultados inesperados. Es por ello, que jasmine cuenta con un sistema de espías (spyOn), cuyo objetivo principal es interceptar la ejecución de una función y simular su resultado.
Veamos un poco de código:
El primer método que vamos a probar es el de obtener el mes actual currentMonth. Si la prueba la creamos en mayo, el código debería ser:
describe('MonthCalculator', () => { it('returns the current month', () => { // Arrange const monthCalculator = new MonthCalculator(); // Act const month = monthCalculator.currentMonth(); // Assert expect(month).toBe('May'); }); });
Este código tiene un problema. La función currentMonth va a retornar un valor que depende del mes actual.
Como ya te podrás imaginar, el uso de espías resulta bastante útil en este caso debido a que podemos controlar el valor del mes retornado.
Es decir:
describe('MonthCalculator', () => { it('returns the current month', () => { // Arrange const monthCalculator = new MonthCalculator(); const spy = spyOn( monthCalculator.api, 'currentMonth' ).and.returnValue('Cristian Month'); // Act const month = monthCalculator.currentMonth(); // Assert expect(month).toBe('Cristian Month'); expect(spy).toHaveBeenCalled(); }); });
La instancia del API la tenemos almacenada en la variable this.api de nuestra clase. Jasmine nos permite interceptar el llamado a nuestro API utilizando la función spyOn. Para ello, como primer parámetro debemos pasar el objeto que contiene el método que vamos a interceptar y como segundo parámetro el nombre del mismo.
Una vez creado nuestro espía, para poder controlar el valor retornado debemos concatenar lo siguiente: .and.returnValue().
Finalmente Jasmine nos permite verificar la ejecución de la función por medio del operador .toHaveBeenCalled().
Cómo reto ve al siguiente enlace, has un fork del proyecto y realiza las pruebas relacionadas con el método nextMonth(). Recuerda crear un espía para eliminar la dependencia con nuestra API.
Finalmente, te dejo un listado de preguntas y respuestas sobre todo lo que puedes hacer con espías:
¿Cómo se crea un espía?
spyOn(obj, 'method') // obj.method es una función
¿Cómo verificar que un método fue llamado?
const ref = spyOn(obj, 'method'); expect(ref).toHaveBeenCalled(); // O directamente expect(obj.method).toHaveBeenCalled()
¿Cómo verificar que un método fue llamado con un parámetro específico?
const ref = spyOn(obj, 'method'); expect(ref).toHaveBeenCalledWith('foo', 'bar'); // O directamente expect(obj.method).toHaveBeenCalledWith('foo', 'bar')
¿Cómo puedo verificar el número exacto de ejecuciones de un método?
expect(obj.method.callCount).toBe(2)
¿Cómo espiar en un método sin modificar su comportamiento?
spyOn(obj, 'method').andCallThrough()
¿Cómo puedo cambiar el valor retornado por un método?
spyOn(obj, 'method').andReturn('value')
¿Cómo puedo sobreescribir un método?
spyOn(obj, 'method').andCallFake(() => 'this is a function');
Carlos Alejandro Hernández Mejía
Ricardo Bladimir Estupinián López
David Moreno
David Toca
David Behar
Vladyslav Kuchmenko
Andrés Lauga
María Auxiliadora de Jesús Andrade R
Ricardo Celis
ProfesorMaría Auxiliadora de Jesús Andrade R
Jeferson Arley Salazar Bolaños
Kevin Costa
Jorge Bou-saad
Tanelo Mondaca Silva
Wilson Alejandro Gonzalez Gaitan
Damian Spizzirri
Damian Spizzirri
Damian Spizzirri
Jesús Ochoa
Por acá dejo mi código
import { MonthCalculator } from './months'; describe('MonthCalculator', () => { it('returns the current month', () => { const m = new MonthCalculator(); expect(m.api.currentMonth()).toBe('May'); }); it('returns the current month', () => { const m = new MonthCalculator(); const spy = spyOn(m.api, 'currentMonth').and.returnValue('First Month'); expect(m.api.currentMonth()).toEqual('First Month') expect(spy).toHaveBeenCalled(); }); it('returns next month', () => { const m = new MonthCalculator(); expect(m.getNextMonth()).toBe('June'); }); it('returns next month', () => { const m = new MonthCalculator(); const spy = spyOn(m.api, 'nextMonth').and.returnValue('Second Month'); expect(m.getNextMonth()).toBe('Second Month'); expect(spy).toHaveBeenCalled(); }); it('should spy and verify', () => { const monthCalculator = new MonthCalculator(); const getNextMonth = spyOn(monthCalculator, 'getNextMonth').and.callThrough(); expect(monthCalculator.getNextMonth()).toBe('June'); expect(getNextMonth).toHaveBeenCalled(); }); it('should spy and verify', () => { const monthCalculator = new MonthCalculator(); spyOn(monthCalculator, 'getNextMonth').and.returnValue('Cristian Month'); expect(monthCalculator.getNextMonth()).toBe('Cristian Month'); }); });
:O
Por favor revisar la redacción de este artículo, algunas frases están cortadas o incompletas.
Gracias por el reporte, ya lo vamos a revisar.
Estoy batallando por entender cómo funcionan los espías, para eso intenté hacer un espía basado en el ejercicio que se muestra acá pero más realista (que sí regrese el verdadero mes), .
No estoy pudiendo fakear la fecha para que regrese lo que quiero, ¿alguna recomendación?
. Código fuente: https://github.com/behagoras/jasmine-tests/tree/currentDate
months.js
function getMonthApi() { this.monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; this.currentDate = new Date() this.cmonth = this.currentDate.getMonth() this.currentMonth = function () { return this.monthNames[this.cmonth-1] } this.nextMonth = function () { return this.monthNames[this.cmonth] } } function MonthCalculator() { this.api = new getMonthApi(); this.getNextMonth = function() { return this.api.nextMonth(); } this.getCurrentMonth = function() { return this.api.currentMonth(); } }
months.spec.js
module.exports = MonthCalculator const MonthCalculator = require('../../src/components/months') describe('Month Calculator',()=>{ it('Should return current month',()=>{ const monthCalculator = new MonthCalculator() const date = new Date("June 17, 1995 03:24:00") monthCalculator.api.currentDate=date const spy = spyOn(monthCalculator.api, 'currentDate').and.resolveTo(date) console.log('monthCalculator.getCurrentMonth()',monthCalculator.getCurrentMonth()) const y = monthCalculator.getCurrentMonth(); expect(y).toBe('June') }) })
Prueba a utilizar:
<code> .and.returnValue(date); </code>
en vez de:
<code> .and.resolveTo(date); </code>
Hay errores en la redacción y en el código de este artículo. Por ejemplo cuando declaran:
const month = monthCalculator.currentMonth();
debería ser:
const month = monthCalculator.getCurrentMonth();
"Cómo reto ve al siguiente enlace, has un fork del repo ..."
Cuál enlace??
ya le aviso al team para que ajusten esta clase, gracias por avisar Mauxi
Ok estaré pendiente para realizar el repo
Esta lección está adelantada, ni siquiera se ha explicado como configurara Jasmine en el entorno y ya están poniendo retos...
Me frustré muy feo con este curso :( me voy a cursar js de nuevo
No entendi nada...
horrible esta clase no debería estar aquí
¿ Por que no todo se maneja con git hub?
Creo que no entendi la ultima pregunta. Yo con el CallFake puedo reescribir la funcion para que haga lo que yo quieaa??
Descarge el projecto, ejecute npm install y despues ng serve.
Me sale este error The serve command requires to be run in an Angular project, but a project definition could not be found
Que onda??
Puede ser que al proyecto le falte el archivo angular.json??
este ejemplo esta malo, empece a realizar las pruebas en una pagina que corre jasmine con karma y de entrada
this.api = new getMonthApi(); // instead this.api = new GetMonthApi();
simple en el test :
<code> // Act const month = monthCalculator.currentMonth(); // esta mal es : // Act const month = monthCalculator.getCurrentMonth(); </code>