Curso de Testing Javascript Moderno. Parte 6: tests unitarios en el DOM

29 Oct 2013

Introducción

Continuamos con nuestra revisión de testing moderno tras haber visto cómo trabajar tanto con código asíncrono como con los métodos privados que podríamos encontrar en nuestra aplicación.

En esta ocasión, retomamos nuestra arquitectura MVVM para analizar cómo organizaríamos nuestras pruebas con el fin de cubrir la mayor parte de código posible. En relación a esto, habría que resaltar que testear cada capa de una arquitectura modelo-vista/vista-modelo resulta trivial: únicamente se trata de ir escribiendo los tests necesarios que comprueban aquellas funcionalidades que necesitamos implementar. No obstante, existen algunas recomendaciones concretas que pueden aplicarse a diversos aspectos de Backbone que es conveniente conocer.

Pero las pruebas que vamos a ver no se limitan únicamente a estas aplicaciones más complejas: resultan fundamentales también para pequeños desarrollos habituales como por ejemplo plugins de jQuery o cualquier funcionalidad de nuestra aplicación que termine pintando algo en pantalla. Básicamente se trata del mismo ‘concepto’: coger datos de algún sitio, procesarlos y enviar algo a la pantalla. Los tests unitarios en este escenario nos ayudan a comprobar que realmente estamos generando aquello que esperamos, y que no se rompe nada por el camino.

Veamos cómo conseguir esto:

Testeando la integridad de los objetos

Cuando en un framework como Backbone tratamos con vistas, una de las primeras pruebas que podríamos realizar es la de propia integridad del sistema: es frecuente utilizar la convención de que un determinado método, como en este caso sería el ‘render’, dé como resultado el mismo objeto (en Backbone sería la propia vista) para así permitirnos encadenar otros métodos. Es algo similar a lo que hacen otras muchas bibliotecas, como jQuery, donde cada método suele devolver al mismo objeto permitiendo así realizar varias acciones facilitando la sintaxis:

// jQuery Chaining
$( '#myDiv' ).find( '#myInput' ).addClass( 'selected' ).val( '' ).focus();

Este comportamiento, que como indicamos es común en Javascript, y que nos permite trabajar con un API dinámico y muy cómo puede (y debería) comprobarse fácilmente en nuestros desarrollos mediante un test. Un ejemplo podría ser el siguiente:

describe( "Testing a view", function() {
    beforeEach( function () {
        this.item = new app.myMethod();
    } );
 
  it( "render() should return the view object", function () {
    this.item.render().should.equal( this.item );
  } );
} );

Como podemos ver, en el test se ha realizado una comprobación aparentemente trivial e innecesaria que viene a significar lo descrito anteriormente: comprobar que el método render aplicado sobre un objeto, devuelve el mismo objeto. Si estamos trabajando sobre una biblioteca o framework de tipo jQuery, una prueba como la anterior también es válida para ese encadenado.

Comprobando cambios en el DOM

Una vez que tenemos la seguridad de que nuestro objeto principal permanece íntegro, podemos profundizar con pruebas sobre su contenido, concretamente sobre el tipo de elemento que generan. Backbone, por ejemplo, cuando trabajamos sobre el método ‘render‘ de una vista, genera a su vez un elemento llamado ‘el‘ que posee sus propias funciones/utilidades. Si tenemos en cuenta que dentro de un MV*, la capa de la vista es la encargada de generar ‘algo‘ en el DOM, una nueva comprobación trivial sería el verificar que realmente obtenemos el nodo, componente o estructura que esperamos.

Este tipo de pruebas, como venimos comentando, también puede efectuarse sobre aplicaciones donde generamos elementos DOM de forma programática usando Javascript estándar, jQuery, Underscore o cualquier otra biblioteca.

it( "searching for a node in a render unit test ", function () {
    this.item.render().el.nodeName.should.equal( "LI" );
} );

En el ejemplo anterior, estamos comprobando que el método render devuelve un nodo de tipo ‘elemento de lista’ (

  • ), el cual, con alguna ligera variante, sería igualmente válido para aplicaciones normales (sin arquitecturas MVVM) en la que quisiéramos comprobar que hemos creado un nuevo elemento en pantalla de forma correcta. Algo del tipo:
    it( "testing a new node for DOM", function () {
        var box = $( '<div />' );
        box[0].nodeName.should.equal( "DIV" );
    } );

    NOTA: Tenemos que recordar aquí que para acceder al elemento DOM de un objeto de tipo jQuery, tenemos que hacerlo a través del índice cero. De ahí que en el ejemplo, hallamos utilizado la fórmula box[0] en lugar de simplemente ‘box’.

    Volviendo al ejemplo Backbone, para realizar tests sobre elementos DOM, es posible (y recomendable) utilizar el mismo de jQuery para extraer los datos más fácilmente y tener un mayor abanico de posibilidades a la hora de hacer comprobaciones. Veamos por ejemplo la siguiente batería de pruebas:

    describe( "Using jQuery for testing a render", function () {
        beforeEach( function () {
            this.item = new app.myMethod();
        } );
     
        describe( "Template", function() {
            beforeEach( function () {
                this.item.render();
            } );
     
            it( "should contain a custom text", function () {
                this.item.$el.text().should.have.string( "Hello World" );
            } );
     
            it("should include an anchor", function () {
                this.item.$el.find( "a" ).should.have.length( 1 );
            } );
     
            it( "should include an <input> checkbox", function () {
               this.item.$el.find( "label > input[type='checkbox']" ).should.have.length( 1 );
            } );
     
            it( "should be clear by default", function () {
                this.item.$el.find( "label > input[type='checkbox']" ).is( ":checked" ).should.be.false;
            } );
        } );
    } );

    En este código, si lo leemos, estaríamos comprobando una serie de nodos u objetos DOM para verificar que tienen las características que esperamos. Los ‘it’ nos dan la información de lo que estamos comprobando con lo que volvemos aquí a subrayar el importante papel que tienen los tests a la hora de documentar un código. Si los repasamos, realmente no es necesario volver a indicar qué es lo que estaríamos comprobando:

    • Estamos en el primer ejemplo comprobando que nuestro nuevo elemento (vista en este caso) contiene el texto «Hello World».
    • Continuamos comprobando que nuestro nuevo elemento contiene un (y solo uno) elemento de tipo ‘anchor’.
    • Comprobamos que nuestro nuevo elemento tiene un ‘label’ que hace de padre a otro elemento (y solo uno) de tipo ‘checkbox’.
    • Comprobamos que que el checkbox anterior no está marcado por defecto.

    Obsérvese aquí como se están accediendo a los nodos recién creados mediante jQuery para consultar diversos atributos de interés para el test. Es interesante remarcar como ‘this’ mantiene su valor esperado de ‘forma inteligente’ pese a invocarse desde distintos ámbitos o ‘clousures’.

    El código fuente de la capa de la vista que permite pasar los tests anteriores sería similar a:

    app.myMethod = Backbone.View.extend( {
        tagName: "li",
        template: _.template(
            /* The markup with anchor, inputs, etc... */
        ),
        render: function () {
            this.$el.html( this.template() );
            return this;
        }
    } );

    Con esto, crearíamos la propiedad ‘$el’ que utilizamos más arriba para extraer los diversos atributos con jQuery. El uso de la ‘$’ como prefijo responde al estándar de anteponer este signo a todos los objetos de tipo jQuery para su rápida identificación.

    También puede aquí verse cómo Mocha permite la anidación de casos de tests (‘describe(s)’) para facilitar la organización de los mismos.

    Conclusión

    Con estas ideas, podemos comenzar a comprobar en nuestras aplicaciones acciones sencillas como que estamos generando con nuestras funciones y métodos aquellos elementos que se esperan en el DOM. Esto es especialmente interesante en RIAs o, mucho más frecuentemente, en plugins para bibliotecas al uso como jQuery.

    Habitualmente es muy frecuente utilizar bibliotecas de manejo de DOM que nos permiten generar listas dinámicas, tablas, carruseles, diapositivas, formularios, ventanas, modales, etc y que se traducen finalmente en la insercción de elementos. Es el mismo caso que sucede con las vistas en las arquitecturas MV*: cargamos y procesamos contenido que finalmente hay que pintar en pantalla. Comprobar que estamos generando el tipo de estructura que esperamos es uno de los tests más básicos que podemos ya ir realizando, pero también de los más interesantes: ¿está nuestro plugin de tipo slideshow generando una lista correcta con las imágenes que le llegan por AJAX? ¿Está nuestra magnífica biblioteca de ventanas modales generando un contenedor con su correspondiente cabecera y pie?

    Este tipo de pruebas nos pueden ir poniendo sobre la marcha y darnos las claves que nos permitan evitar futuros bugs.

  • Más:

    {2} Comentarios.

    1. Leifsk8er

      Se va poniendo interesante, y genial que sea con jQuery!

      Por otro lado profundizando un poco más por internet he empezado a encontrar nombres de herramientas que parecen muy ligados hoy al desarrollo moderno javascript y TDD, algunas conocidas otras no tanto, pero que al final parece que hay que tener un poco dominadas todas si se quiere ser eficiente en el desarrollo: Git, GruntJS o YeoMan JS, KarmaJS y Mocha, PhantomJS, Travis CI, Istambul | Sonar | Js Coverage. Si más adelante, en un capitulo final pudiese tratarse el tema de analizar cobertura y trabjo con un servidor de integración continua para automatizar esto seria genial.

      Un saludo!

      • Carlos Benítez

        Hola Leif;
        no te preocupes, el tema de la cobertura está planteado y lo trataremos: no usaremos sin embargo JSCover por ciertos problemas que también veremos. En su lugar, recurriremos a Karma. De la mano de esta herramienta, PhantomJS nos servirá para las pruebas desatendidas, por lo que también está en el lote.

        La integración continúa también la trataremos, utilizando para ello Grunt.

        Finalmente, Yeoman no creo que lo toquemos aunque si lo mencionaremos: al tratarse de una herramienta todo en uno, es interesante, pero no es útil desde un punto de vista didáctico. Quizá le dedique un post completo fuera ya de la serie.

        Como ves, más o menos está todo previsto!
        Saludos.

    Deja un comentario

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *