@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

Testing práctico con JavaScript

By Ramón Guijarro

Testing práctico con JavaScript

  • 1,742