Testeando Componentes de React

Escrito por

@EmaSuriano

Por que escribir Unit Tests?

  • Documentar las responsabilidades de un componentes (contrato). 
  • Verificar que los cambios hechos no modificaron el comportamiento esperado.
  • Facilitar las pruebas de componentes en ambiente totalmente aislado.

Que testear?

Contrato de un Componente

  • Define el comportamiento y uso esperado de un componente.
  • Sin un contrato claro, un componente se vuelve dificil de ententer.
  • Escribiendo test es una buena manera de definir formalmente el contrato de un componente.

Cómo esta compuesto?

  • Qué es lo que renderiza.
  • Las props que recibe.
  • El state que maneja.
  • Cómo reacciona ante la interacción con el Usuario (clickeando, escribiendo, etc.).

Partes menos frecuentes

  • El contexto en el cual se renderiza.
  • Llamados a funciones externas (librerías).
  • Efectos secundarios como parte del ciclo de vida.

Preguntas para describir el contrato

  • Qué componentes renderizo? Qué valores les paso?
  • Qué es lo que hago con las props que recibo?
  • En el caso de utilizar el state: Cuándo lo actualizo y para qué?
  • En el caso de que el usuario interactue conmigo o algún componente llame a una callback que le envié: Qué es lo sucede?
  • Qué es lo que pasa en los eventos del Ciclo de Vida?

Herramientas

Jest

Unit Testing framework

Características

  • Performance - Jest corre los test en paralelo, minimizando el tiempo de ejecución.
  • Snapshot testing - Se compare el resultado de un componente renderizado contra un archivo (No se va a tratar en esta charla).
  • Code coverage support - Esta integrado con Jest.
  • Integración con otras librerias (e.g. Enzyme, Chai).

Funciones más usadas

  • describe(text, func): define una suite de test.
  • it(text, func): define un test.
  • BeforeEach(func): declara una funcion que deberá ser llamada antes de cualquier test.
  • AfterEach(func): igual que BeforeEach, pero después de cualquier test.
  • expect(any): crea una afirmación que sera validada
    • toEqual(any): compara si dos objetos tienen el mismo valor.
    • toBe(any): compara dos objetos mediante ===.

Aclaración: Jest tiene manejo de funciones mockeadas, pero estas seran manejadas con Sinon

Enzyme

Renderiza un componente en un entorno virtual

Shallow

Realiza un render en sólo un nivel de profundidad del componente.

Mount

Pros:

  • Permite comunicarnos con los componentes hijos.

Contras:

  • El test demora más.
  • Si los hijos son containers, hay que enviar un store mockeado

Realiza un Full DOM rendering del componente.

Pros:

  • Sólo se renderiza el componente a testear.
  • No nos tenemos que preocupar por qué pasar a los hijos.

Contras:

  • No podemos interacturar con los hijos.

Comparacion entre Shallow y Mount

const Label = ({ text }) => <label>{text}</label>;
const SubmitButton = ({ text }) => <button><Label text={text} /></button>;

Shallow

Mount

<button>
    <Label text="Hello!" />
</button>
<button>
    <label>
        Hello!
    </label>
</button>

Funciones más importantes

  • find( selector ) :: Wrapper
  • contains( ReactElement ) :: Boolean
  • parent() :: Wrapper
  • at(index) :: Wrapper
  • state( key ) :: Any
  • prop( key ) :: Any
  • simulate( event, data) :: void
  • setProps(nextProps) :: Wrapper
  • setState(nextState) :: Wrapper
  • instance() :: ReactComponent
  • update() :: Wrapper

Que es un selector?

  • Un selector de CSS:
    • class syntax --> .find(".foo")
    • tag syntax --> .find("input")
    • id syntax --> .find("#name")
    • prop syntax --> .find("[alt=house]")
  • Componente de React --> .find(Label)
  • Nombre de Componente de React --> .find('Label')
  • Props de un Componente --> .find( { text: foo } )

Instance()

  • Solamente puede ser llamada desde el componente root o padre (el que se monto con enzyme).
  • Devuelve un componente React al que se le pueden hacer llamados a funciones internas. 
    • Muy util cuando queremos probar una funcion que sólo es pasada a los componentes hijos (callback).

Sinon

Mocking library

Spy

Ofrece informacion sobre los llamados de las funciones: si fue llamado, cuántas veces, con qué parámetros, etc.

Ejemplo

function myFunction(callback, number){
    callback(number + 1);
}

describe('myFunction', function() {
  it('should call the callback function', function() {
    var callback = sinon.spy(); //create instance of spy

    myFunction(callback, 10); //send as parameter

    assert(callback.calledOnce); //check if it was called
    assert(callback.calledWith(11)); //check with what parameters was called
  });
});

Cuando usar Spy?

  • Comprobar la cantidad de veces que una función fue llamada.
  • Comprobar los argumetnos que son pasados a una función.

Stub

Proveen una manera de reemplazar el comportamiento de una función, pudiendo retornar valores o excepciones.

Ejemplo

const userService = {
    function makeFetch(id) {
        return fetch('users/' + id);
    }
}

describe('myFunction', function() {
  it('should call the callback function', function() {
    var callback = sinon.stub(userService, 'makeFetch'); // create instance of stub
    callback.callsFake((id) => ({ id, name: 'userName' })); // redefine behaviour

    const result = userService.makeFetch(10);

    assert(result).toEqual({ id: 10, name: 'userName' })
    callback.restore(); //IMPORTANT! --> restore behaviour of userService.makeFetch
  });
});

Cuando usar Stub?

  • Reemplazar piezas de código problemáticas, que no tienen relación con el componente.
  • Llamar partes de código que no se hubiesen llamado, como puede manejo de errores.
  • Ayuda a la prueba de código asincrónico (Fetch, Ajax request, etc.).

Mock

Proveen una manera de reemplazar todo un objeto facilmente, combinando spies y stubs.

Cuando usar Mock?

  • Cuando se tengan que utilizar uno o varios stubs/spies, y verificar comportamientos especificos de los mismos:
    • Veces llamadas.
    • Argumentos que se les pasaron.
    • Valores que retornaron.

 Mock vs Stub-Spy

function incrementStoredData(value, amount){
  var total = store.get(value) || 0;
  var newtotal = total + amount;
  store.set(value, newtotal);
}

describe('incrementStoredData', function() {
  it('should increment stored value by one - using Mock', function() {
    var storeMock = sinon.mock(store); // create instance of Mock
    storeMock.expects('get')
        .withArgs('data')
        .returns(0); //redefine what it has to returns and set validation
    storeMock.expects('set')
        .once()
        .withArgs('data', 1); //set validation for the set function

    incrementStoredData('data', 1);

    // IMPORTANT! 
    storeMock.restore(); // restore to the default behaviour
    storeMock.verify(); // this line runs all the validation from above
  });

  it('should increment stored value by one - using Stub and Spy', () => {
    const getStub = sinon.stub(store, 'get').callsFake(() => 0);
    const setSpy = sinon.spy(store, 'set');
    
    incrementStoredData('data', 1);

    assert.equal(getStub.calledWith('data'), true);
    assert.equal(setSpy.calledOnce, true);
    assert.equal(setSpy.calledWith('data', 1), true);    
    getStub.restore();
    setSpy.restore();
  });
});

FakeTimers

Nos dejan "viajar en el tiempo", para probar eventos que dependan del tiempo en los componentes.

Ejemplo

it('should check if timer ends when it has to', () => {
    var clock = sinon.useFakeTimers(); // create instance of fakeTimer

    var result = "timer running";
    setTimeout(() => {
        result = "time's out!";
    }, 1000); // When 1 second has passed, result will take the value of "time's out!"

    clock.tick(1000); //make 1 second passed with the fakeTimer
    assert.equal(result, "time's out!");
});

Ejemplos de Tests

Preguntas ?

Gracias!

Testeando componentes de React

By Emanuel Suriano

Testeando componentes de React

Introduccion de testing de componentes de React.

  • 556