Curso de Testing Javascript Moderno. Parte 7: más DOM con Fixtures y JSDom

05 Nov 2013

Introducción

En los capítulos anteriores de nuestra serie sobre testing moderno, vimos cómo se pueden realizar tests independientes tanto sobre modelos (recurriendo para la mayoría de los casos a stubs, spies o mocks) y sobre las vistas (comprobando el marcado con jQuery y la coherencia en la lógica de Backbone). También hemos aclarado que, aunque estemos hablando de una arquitectura tipo MVVM, estos tests son ‘exportables’ a proyectos de tipo estándar sin ningún tipo de arquitectura compleja detrás.

Retomamos ahora por un momento la idea de que estamos trabajando en un entorno donde contamos con una capa de tipo modelo que queda conectada a otra de tipo vista, permitiendo así que ambas se itercambien datos de forma bidireccional. Ese sería el planteamiento básico de una bibilioteca o framework como Backbone, donde de nuevo los tests deben asegurar que los dos bloques quedan efectivamente interconectados. Toca ahora entonces revisar la interacción de ese marcado de tipo DOM que hemos generado en el capítulo anterior para verificar que se conecta con la vista y que las acciones realizadas sobre uno implican los cambios esperados en el otro.

Fuera de la arquitectura MVVM, lo que queremos comprobar ahora es que un marcado creado para un test, como por ejemplo un botón, al manipularse (pulsarlo), lanzan algún evento o dispara una lógica esperada (una petición AJAX o limpiar un formulario)… Veamos cómo podemos conseguir esto.

Fixtures

Para este tipo de pruebas sobre elementos ‘físicos’, como por ejemplo botones, o rellenar campos de un formulario y similares, es frecuente ver herramientas externas como Selenium. Selenium, quizá la más popular, es una aplicación que funciona en combinación con un navegador (a modo de plugin por lo general) y que permite configurarla para simular que una persona está utilizando la aplicación: ‘vete al botón y púlsalo’, ‘ve al campo de formulario y escribe dentro esta cadena’. El problema, es que esta herramienta es ‘literal’: es casi como grabar un vídeo con acciones que repetirá después de forma automática (la típica macro)… tras echarle un vistazo, queda claro que debe haber algo más preciso y que permita un mayor control sobre las eventualidades que podamos encontrar.

Y lo hay; para estos trabajos únicamente es necesario contar con algún tipo de herramienta que lance aquellos eventos frente a los que nuestra lógica reacciona, aunque los disparadores de esos eventos, como nuestro botón imaginario, ni siquiera exista porque no hay DOM. Podemos así recurrir de nuevo a jQuery para crear en la propia página donde corren los tests aquellos elementos DOM que precisan ser manipulados y simular que estamos en el entorno real de la aplicación: se tratará en realidad de una maqueta donde puede haber botones, campos de texto, etc, pero sin ‘diseño’ ni ‘adornos’; solo los elementos clave que pueden ser manipulados a la espera de que algo interactúe con ellos. A este tipo de contenedores o falsos elementos introducidos únicamente para emular la UI original, se les conoce como «fixtures».

Creando un DOM temporal donde insertar nuestros falsos actores

Para poder introducir elementos en el DOM dentro de nuestros tests, el primer paso lógico es contar con un DOM. Esto resulta trivial en un entorno de navegador, donde éste de forma automática ya nos levanta uno al cargar cualquier tipo de contenido HTML. Sin embargo, si estamos trabajando desde la línea de comandos de forma desatendida (lo habitual por ejemplo en un entorno de Integración Continua -CI-), no hay navegador por medio y, como cabe esperar, tampoco DOM.

Para salvar este problema, no queda más remedio que emular ese navegador que no tenemos y, para ello, usaremos otra biblioteca habitual a día de hoy en el mundo del testing Javascript como es JSDom.

JSDom

JSDom es una herramienta que nos permite recrear un DOM dentro de un entorno en el que no contamos con un navegador. Este DOM podemos tanto tomarlo de un fichero externo como crearlo desde cero mediante los métodos que este script proporciona.

Un ejemplo de cómo JSDom toma un fichero para recrear a partir del mismo el DOM sería:

// Count all of the links from the nodejs build page
var jsdom = require( "jsdom" );
 
jsdom.env(
    "http://nodejs.org/dist/",
    [ "http://code.jquery.com/jquery.js" ],
    function ( errors, window ) {
        console.log( "there have been", window.$( "a" ).length, "nodejs releases!" );
    }
);

En el ejemplo anterior, iniciamos jsdom y pasamos a trabajar sobre su método env(), el cual genera el entorno (environment): le indicamos una ruta donde pueda encontrar un marcado HTML válido y las dependencias que requerimos. En este caso, la plantilla HTML se corresponde con «http://nodejs.org/dist/» y la dependencia sería el CDN de jQuery «http://code.jquery.com/jquery.js». Finalmente, como callback, añadimos la función donde ya disponemos de todos los elementos listos para trabajar con ellos.

Resumiendo, el esquema sería:

jsdom.env(
    "my-template.html",
    [ "external-libs.js" ],
    function ( errors, window ) {
        // Callback when all be ready
    }
);

Recrear un DOM desde cero, sin el uso de una plantilla externa, implica modificar únicamente el primer parámetro del método «env()» con el marcado deseado:

var jsdom = require( "jsdom" );
 
jsdom.env( {
    html: "<html><body></body></html>",
    scripts: [
        'http://code.jquery.com/jquery-1.5.min.js'
    ]
}, function ( err, window ) {
    var $ = window.jQuery;
    $( 'body' ).append( "<div class='testing'>Hello World</div>" );
    console.log( $( ".testing" ).text() ); // outputs Hello World
} );

NOTA: En este segundo ejemplo, además, comprobamos cómo el método «env()» puede también recibir un objeto para configurar sus propiedades.

Con esto, tendríamos ya nuestra UI emulada desde la línea de comandos lista para interactuar con ella.

Preparando los ficheros de tests para correr en CLI

Cuando ejecutamos tests en CLI (línea de comandos), hay que tener en cuenta que Node maneja las variables de entorno de un modo diferente a como lo hace el navegador. Para evitar errores con las dependencias, tenemos que modificar nuestro fichero de test de modo que si estamos en modo Node, estos scripts carguen de un modo diferente:

var jQuery = jQuery || require( "jquery" ),
    _ = _  || require( "underscore" ),
    Backbone = Backbone || require( "backbone" );
Backbone.$ = jQuery;

En el fragmento anterior, mediante el recurso de la evaluación por cortocircuito en Javascript, se reasignan las dependencias en caso de ser necesario: si por ejemplo jQuery no se encuentra, se ‘trae’ directamente con Require.

Del mismo modo, para que el contexto ‘window‘ esté disponible en los tests, crearíamos nuestra «fixture«:

if ( typeof window === undefined ) {
    var jsdom = require("jsdom").jsdom,
        doc = jsdom('<html><body></body></html>'),
        window = doc.createWindow();
}

NOTA: Es interesante recordar aquí como el objeto window, es específico del contexto de un navegador. Fuera de ese entorno no existe, y por ello tenemos que recrearlo/emularlo si vamos a utilizarlo (algo que si no hacemos nosotros de forma consciente, si que lo harán bibliotecas de terceros como cualquier plugin de jQuery).

Y con este entorno ya desplegado, la creación de tests unitarios sería idéntica a lo ya visto. Por ejemplo:

describe( 'Testing DOM in Node', function () {
    it( 'should be initialized', function () {
        $( 'body' ).should.have.length( 1 );
    } );
} );

Conclusión

Cuando queremos comprobar el funcionamientos de la UI de nuestra aplicación, como por ejemplo la acción que ha de ocurrir al pulsar un botón, tenemos que ser capaces de recrearla en un entorno donde ni siquiera tenemos navegador. Conseguirlo es sencillo: disponemos de herramientas que emulan todos los elementos con los que cuenta un navegador y que nos permiten inyectar esa UI de forma puntual para inmediatamente poder interactuar con sus componentes.

Esta forma de hacer las cosas no es la única y, como veremos más adelante, podemos incluso delegar todo este trabajo a otras herramientas como Karma y PhantomJS las cuales se encargarán de automatizar parte de este proceso. Sin embargo, por el momento, es interesante conocer los conceptos básicos para ir avanzando con nuestras pruebas.

Más:

Solo un marciano comentando!

  1. Jordi

    Genial tutorial!
    He estado siguiéndolo y he conseguido realizar tests de jquery sobre mi código. Los tests funcionan de lujo, ya que puedo ir lanzando eventos que provocan modificaciones de clases según lo esperado.

    El tema está en que no consigo que se publiquen resultados de cobertura del mismo, pese a que sí he instrumentalizado mediante jscoverage en el Makefile:
    @jscoverage –no-highlight ../webroot/js test-cov

    Y luego utilizado en el jsdom los ficheros instrumentalizados:
    jsdom.env(
    htmlCode,
    js_exports,
    function (errors, window) {
    // tests
    }
    );

    Para testear, se lanzan desde el Makefile como:
    @NODE_ENV=test ./node_modules/.bin/mocha \
    –reporter html-cov \
    –ui tdd > public/coverage.html

    Y el resultado es que coverage.html muestra ‘0 files’.

    ¿Habeis encontrado alguna manera de que node-jscoverage se entere de que ese código se ha ejecutado?

Deja un comentario

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