¿Cómo probar una clase ES6 que necesita jquery?

8 minutos de lectura

¿Como probar una clase ES6 que necesita jquery
eldeliciousmuffin

Tengo un módulo ES6 que necesita jquery.

import $ from 'jquery';

export class Weather {
    /**
     * Constructor for Weather class
     *
     * @param latitude
     * @param longitude
     */
    constructor(latitude, longitude) {
        this.latitude  = latitude;
        this.longitude = longitude;
    }

    /**
     * Fetches the weather using API
     */
    getWeather() {
        return $.ajax({
            url: 'http://localhost:8080/weather?lat=" + this.latitude + "&lon=' + this.longitude,
            method: "GET",
        }).promise();
    }
}

El módulo funciona bien cuando lo uso en mi main pero el problema es con la prueba que estoy escribiendo para él.

Aquí está la prueba:

import {Weather} from '../js/weather';
import chai from 'chai';
import sinon from 'sinon';

chai.should();

describe('weatherbot', function() {
    beforeEach(function() {
        this.xhr = sinon.useFakeXMLHttpRequest();

        this.requests = [];
        this.xhr.onCreate = function(xhr) {
            this.requests.push(xhr);
        }.bind(this);
    });

    afterEach(function() {
        this.xhr.restore();
    });

    it('should return a resolved promise if call is successful', (done) => {
        let weather = new Weather(43.65967339999999, -79.72506369999999);

        let data="{"coord":{"lon":-79.73,"lat":43.66},"weather":[{"id":521,"main":"Rain","description":"shower rain","icon":"09d"}],"base":"stations","main":{"temp":15.28,"pressure":1009,"humidity":82,"temp_min":13,"temp_max":17},"visibility":24140,"wind":{"speed":7.2,"deg":30},"clouds":{"all":90},"dt":1496770020,"sys":{"type":1,"id":3722,"message":0.0047,"country":"CA","sunrise":1496741873,"sunset":1496797083},"id":5907364,"name":"Brampton","cod":200}";

        weather.getWeather().then((data) => {
            expect(data.main.temp).to.equal(15.28);
            done();
        });

        this.requests[0].respond("GET", "/weather?lat=43.659673399999996&lon=-79.72506369999999", [
            200, {"Content-Type":"application/json"}, JSON.stringify(data)
        ]);
    });
});

Y aquí está mi package.json:

{
  "devDependencies": {
    "babel-core": "^6.24.1",
    "babel-loader": "^6.1.0",
    "babel-polyfill": "^6.3.14",
    "babel-preset-es2015": "^6.1.18",
    "chai": "^3.5.0",
    "copy-webpack-plugin": "^0.2.0",
    "css-loader": "^0.28.0",
    "extract-text-webpack-plugin": "^2.1.0",
    "file-loader": "^0.11.1",
    "mocha": "^3.4.1",
    "mocha-webpack": "^1.0.0-beta.1",
    "qunitjs": "^2.3.2",
    "sinon": "^2.2.0",
    "style-loader": "^0.16.1",
    "svg-inline-loader": "^0.7.1",
    "webpack": "*",
    "webpack-dev-server": "^1.12.1",
    "webpack-node-externals": "^1.6.0"
  },
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch --display-error-details",
    "start": "webpack-dev-server --hot --inline --port 8383",
    "test": "mocha --compilers js:babel-core/register ./test/*.js",
    "test:watch": "npm run test -- --watch"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  },
  "dependencies": {
    "bootstrap": "^3.3.7",
    "jquery": "^3.2.1",
    "webpack": "*"
  }
}

Como puedes ver solo tengo que hacer npm test para ejecutar la prueba.

cuando hacer npm test, me sale este error:

TypeError: _jquery2.default.ajax is not a function
      at Weather.getWeather (js/weather.js:19:18)
      at Context.<anonymous> (test/index.js:26:17)

Pero estoy importando el jquery en el módulo, ¿por qué podría estar sucediendo?

¿Como probar una clase ES6 que necesita jquery
oligofreno

Existen dos principales problemas aquí. El primero es, por supuesto, que necesita solucionar su problema de importación, pero eso no está relacionado con las pruebas. Deberá resolver esto antes de pasar a la prueba, y esto podría tener que ver con la configuración de su herramienta de compilación en lugar de ejecutarla en Node. Debe abrir una pregunta separada para esto, aunque esto podría ser de ayuda. Probablemente todo lo que necesita hacer es reemplazar la importación con este import * as jQuery from 'jquery';

El otro gran problema es que lo está ejecutando dentro de Nodo (utilizando npm test que activa Mocha) mientras su código requiere un navegador. La implementación del servidor falso de Sinon está destinada a ser utilizada en un entorno de navegador, y está ejecutando las pruebas en un entorno de servidor. Eso significa que ni jQuery ni la configuración del servidor falso funcionarán, ya que Node no tiene un Objeto XHR.

Entonces, aunque la configuración de Sinon XHR parece estar bien, a menos que esté dispuesto a cambiar su corredor de prueba para ejecutar sus pruebas dentro de un entorno de navegador (Karma es excelente para hacer esto desde la CLI), debe manejar esto de otra manera. Rara vez alcanzo a falsificar XHR y, en cambio, elimino las dependencias en un nivel superior. La respuesta de @CarlMarkham toca esto, pero no entra en detalles sobre cómo funcionaría esto con su código.

Básicamente, te quedan dos opciones al ejecutar tu código en Node:

  1. Intercepte las llamadas que importan el módulo JQuery y reemplácelo con su propio objeto que tiene una versión stub de ajax. Esto requiere un interceptor de cargador de módulos como rewire o proxyquire.
  2. Use la inyección de dependencia directamente en su módulo.

La página de inicio de Sinon tiene un buen artículo de Morgan Roderick en la primera opción, así como varias enlaces a otros artículos en otras partes de la red, pero no cómo eso explica cómo hacer la primera opción. Debería escribir uno cuando tenga tiempo… pero aquí va:

Usando inyección de dependencia en el nivel de instancia

La forma menos invasiva es simplemente exponer el ajax método en la instancia que está probando. Eso significa que no necesitará inyectar nada en el módulo en sí, y no tendrá que pensar en la limpieza después:

// weather.js
export class Weather {
    constructor(latitude, longitude) {
        this.ajax = $.ajax;
        this.latitude  = latitude;
        this.longitude = longitude;
    }

    getWeather() {
        return this.ajax({ ...

// weather.test.js

it('should return a resolved promise if call is successful', (done) => {
    const weather = new Weather(43.65, -79.725);
    const data="{"coord":{"lon":-79.73, ... }" // fill in
    weather.ajax = createStub(data);

he escrito un ejemplo más elaborado de esta técnica en el problema de Sinon rastreador.

Hay otra forma, que es más invasiva, pero te permite mantener el código de la clase inalterado modificando directamente las dependencias del módulo:

Usando la inyección de dependencia en el nivel del módulo

Simplemente modifique su clase Weather para exportar una interfaz de establecimiento para sus dependencias para que puedan sobrescribirse:

export const __setDeps(jQuery) => $ = jQuery;

Ahora puede simplificar su prueba para que se lea así:

import weather from '../js/weather';
const Weather = weather.Weather;

const fakeJquery = {};
weather.__setDeps(fakeQuery);

const createStub = data => () => { promise: Promise.resolve(data) };

it('should return a resolved promise if call is successful', (done) => {
    const weather = new Weather(43.65, -79.725);
    const data="{"coord":{"lon":-79.73, ... }" // fill in
    fakeQuery.ajax = createStub(data);

    weather.getWeather().then((data) => {
        expect(data.main.temp).to.equal(15.28);
        done();
    });
}

Un problema con este enfoque es que está manipulando las partes internas del módulo, por lo que necesita restaurar el objeto jQuery en caso de que necesite usar la clase Weather en otras pruebas. Por supuesto, también podría hacer lo contrario: en lugar de inyectar un objeto jQuery falso, puede exportar el real objeto jQuery y modificar el ajax método directamente. Luego, eliminaría todo el código de inyección en el código de muestra anterior y lo modificaría para leer algo como

// weather.js
export const __getDependencies() => { jquery: $ };


// weather.test.js

it('should return a resolved promise if call is successful', (done) => {
    const weather = new Weather(43.65, -79.725);
    const data="{"coord":{"lon":-79.73, ... }" // fill in
    
    __getDependencies().jquery.ajax = createStub(data);

     // do test
     
     // restore ajax on jQuery back to its original state

  • No he tenido tiempo de analizar esto correctamente ya que estoy viajando mucho en estos días, pero creo que esa es la respuesta que más o menos estaba buscando. Pregunta rápida, ¿cuál sería una buena herramienta de compilación para comenzar con el desarrollo de aplicaciones web modernas? ¿Paquete web?

    – el delicioso muffin

    13 jun.

  • Webpack está bien, pero la configuración siempre es una tarea. Tratar create-react-app para obtener toda la configuración shebang.

    – oligofreno

    13 jun.

A menos que sea el módulo ES6 el que tiene default export que se está importando (que jQuery no lo es), la forma correcta de importarlo es

import * as $ from 'jquery';

La forma en que se tratan los módulos CommonJS con import es responsabilidad de la herramienta de compilación. default las importaciones de módulos que no son ES6 se admitían (erróneamente) al menos en versiones anteriores de Webpack. Y sin restricción de versión para

"webpack": "*"

la dependencia es una forma directa de compilaciones rotas.

  • todavía tengo TypeError: $.ajax is not a function. 🙁

    – el delicioso muffin

    06 jun.

  • ¿Cómo se prueba exactamente? Si es como se dice, "test": "mocha --compilers js:babel-core/register ./test/*.js", entonces esto explicaría muchas cosas, y obviamente no puedes salirte con la tuya.

    – Frasco de estus

    06 jun.

  • mocha runner se ejecuta en Node, no en el navegador. No hay DOM para jQuery en Node. jQuery es una función de fábrica en Node, bugs.jquery.com/ticket/14549 , no tiene ajax propiedad de hecho. Si está dispuesto a ejecutar pruebas en Node, una estrategia adecuada es stub/simular todas las cosas de jQuery, es mejor hacer $ Proveedor angular en cambio, esto mejorará la capacidad de prueba.

    – Frasco de estus

    06 jun.

  • Esto no es correcto. el tratamiento de la module.exports objeto como la exportación predeterminada es el enfoque sugerido. esperando el * as $ objeto de ser module.exports es algo que hacen Babel y Webpack, pero no es lo que hará Node, y no es algo en lo que debas confiar para seguir adelante.

    – loganfsmyth

    06 jun.

  • @loganfsmyth Que yo sepa, este es un comportamiento ad hoc que nunca se estandarizó, me parece más un error que una característica. ¿Qué pasa con los módulos CJS que tienen export.default? Esto ciertamente no agrega claridad. De todos modos, es probable que la importación no sea un problema principal aquí.

    – Frasco de estus

    06 jun.

1641753888 692 ¿Como probar una clase ES6 que necesita jquery
Cjmarkham

En mi proyecto, aplico todos los métodos de jQuery que utilizo, así que apago $.ajax ya que todo lo que necesita probar son las promesas devueltas.

Algo como esto en otro archivo:

module.exports = {
  ajax: sinon.stub(global.$, 'ajax')
}

Entonces, en tu prueba

import { ajax } from 'stubs'

De esta manera, el ajax El método está bloqueado y puede definir lo que debe devolver por prueba:

ajax.returns({
  done: (callback) => { callback(someParam) },
  fail: () => {},
  always: (callback) => { callback() }
});

  • Esto no ayudará al OP. jQuery está vinculado en el código que se probará, por lo que el cartel debe interceptar las llamadas requeridas o inyectar jQuery en el módulo. Solo entonces su código tendrá valor.

    – oligofreno

    11 jun.


.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad