@soyguijarro
Testing práctico con JavaScript
Desarrollador web
Entusiasta de JavaScript y React
Experto en proyectos absurdos
Ramón Guijarro
No soy un experto
No soy un experto
pero tengo opiniones
No soy un experto
pero tengo opiniones
y es una introducción
Herramientas
Estrategias
Más allá
Introducción
Introducción a los tests
Qué son
Código que comprueba que nuestra aplicación funciona como esperamos
console.log
import {add} from './operations'; describe('add', () => { it('should add two numbers', () => { expect(add(4, 5)).toBe(9); }); });
Por qué escribirlos
Prevención de errores
Documentación
Prevención de errores
Documentación
Ayuda al desarrollo
Prevención de errores
Tipos principales
Integración
Funcionales
Unitarios
Unitarios
Integración
UI
Unitarios
Integración
UI
¿
?
Herramientas de test
Testdouble
Karma
CasperJS
Ava
Jest
Tape
Protractor
Mocha
PhantomJS
Puppeteer
Sinon
Jasmine
Chai
Nightwatch
Cucumber
TestCafe
Selenium
Istanbul
Unexpected
Nightmare
WebdriverIO
Tipos principales
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
import {add} from './operations'; describe('add', () => { it('should add two numbers', () => { expect(add(4, 5)).toBe(9); }); });
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
import {add} from './operations'; describe('add', () => { it('should add two numbers', () => { expect(add(4, 5)).toBe(9); }); });
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Spies
Mocks
Stubs
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
it('calls handler when clicked', () => { const clickHandler = spy(); const button = renderButton(clickHandler); button.click(); expect(spy.called).toBe(true); });
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
it('requests avatar and saves it', () => { const fetchAvatar = stub().returns('john.png'); const storage = createStorage(fetchAvatar); storage.add('john'); expect(storage.get('john')).toBe('john.png'); });
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
it('requests avatar and saves it', () => { const mock = mock('fetchAvatar') .expects('fetch') .calledWith('john'); const storage = createStorage(fetchAvatar); storage.add('john'); mock.verify(); });
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
it('should load', async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://google.com'); let text = await page.evaluate( () => document.body.textContent ); expect(text).toContain('google'); });
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Aserciones
Estructura
Dobles de test
Navegador
Cobertura
Experiencia de desarrollador
Developer experience
Configuración
Velocidad
Contexto
Uso
Jest
Jasmine
Ava
Jest
Jasmine
Ava
Jest
Jasmine
Ava
Jest
Jasmine
Ava
Snapshot testing
it('renders correctly', () => {
const tree = renderer.create(
<Link page="http://www.facebook.com">
Facebook
</Link>
).toJSON();
expect(tree).toMatchSnapshot();
});
Estrategias de test
Manejo del DOM
Simulado
Real
Simulado
Real
jsdom
Simulado
Real
Efectos secundarios
Llamadas de red
Asincronía
Temporizadores
test('callback', done => { doStuff(result => { // Assert done(); }); }); test('promise', () => { return doStuff().then(result => { // Assert }); });
Llamadas de red
Asincronía
Temporizadores
Llamadas de red
Asincronía
Temporizadores
Stubs / Mocks
Fake server / requests
it('should count time') = () => { const crono = createChronometer(); crono.start(); const clock = useFakeTimers(); clock.tick(2000); expect(crono.getSecs()).toBe(2); clock.restore(); });
Llamadas de red
Asincronía
Temporizadores
Módulos y dependencias
Sobreescritura
Inyección
Sobreescritura
Inyección
it('requests avatar and saves it', () => { const fetchAvatar = stub().returns('john.png'); const storage = createStorage(fetchAvatar); // This calls fetchAvatar internally storage.add('john'); expect(storage.get('john')).toBe('john.png'); });
Sobreescritura
Inyección
jest.mock('./fetchAvatar'); const fetchAvatar = require('./fetchAvatar'); fetchAvatar.mockImplementation(() => 'john.png'); it('requests avatar and saves it', () => { const storage = createStorage(); // This calls fetchAvatar internally storage.add('john'); expect(storage.get('john')).toBe('john.png'); });
Más allá de los tests
Linters
ESLint
StandardJS
Type checkers
Inferencia
Anotaciones
const multiply = (a: number, b: number): number => a * b;
Inferencia
Anotaciones
const multiply = (a: number, b: number): number => a * b; multiply('x', 2);
Inferencia
Anotaciones
Inferencia
Anotaciones
Cannot call `multiply` with `"x"` bound to `a` because string is incompatible with number
const multiply = (a: number, b: number): number => a * b; multiply('x', 2);
Inferencia
Anotaciones
const multiply = (a, b) => a * b; multiply('x', 2);
Inferencia
Anotaciones
const multiply = (a, b) => a * b; multiply('x', 2);
Cannot perform arithmetic operation because string is not a number
TypeScript
Flow
Integración continua
Travis
Jenkins
CircleCI
Otras técnicas
Pair programming
Code reviews
Conclusiones
Los tests previenen errores y nos ayudan a desarrollar
Unas herramientas para tests funcionales y otras para el resto
Testeamos unidades y la integración entre ellas a través de su colaboración
Usamos dobles para emular todo lo que no es objeto del test
En los tests no funcionales simulamos el DOM y las APIs de navegador
Existen herramientas adicionales que también pueden prevenir errores
Tenemos que valorar coste frente a beneficio y ser pragmáticos
Muchas gracias
@soyguijarro