unitarios unitarias tutorial test pruebas node hacer español javascript unit-testing mocking requirejs

unitarias - test unitarios javascript



¿Cómo puedo simular dependencias para pruebas unitarias en RequireJS? (7)

Tengo un módulo de AMD que quiero probar, pero quiero burlarme de sus dependencias en lugar de cargar las dependencias reales. Estoy usando requirejs, y el código de mi módulo se ve así:

define([''hurp'', ''durp''], function(Hurp, Durp) { return { foo: function () { console.log(Hurp.beans) }, bar: function () { console.log(Durp.beans) } } }

¿Cómo puedo burlar hurp and durp para poder probar eficazmente la unidad?


Entonces, después de leer esta publicación, se me ocurrió una solución que usa la función de configuración requirejs para crear un nuevo contexto para tu prueba donde simplemente puedes burlar tus dependencias:

var cnt = 0; function createContext(stubs) { cnt++; var map = {}; var i18n = stubs.i18n; stubs.i18n = { load: sinon.spy(function(name, req, onLoad) { onLoad(i18n); }) }; _.each(stubs, function(value, key) { var stubName = ''stub'' + key + cnt; map[key] = stubName; define(stubName, function() { return value; }); }); return require.config({ context: "context_" + cnt, map: { "*": map }, baseUrl: ''js/cfe/app/'' }); }

Por lo tanto, crea un nuevo contexto en el que las definiciones de Hurp y Durp se establecerán por los objetos que pasó a la función. El Math.random para el nombre es quizás un poco sucio pero funciona. Porque si tienes un montón de pruebas necesitas crear un nuevo contexto para cada suite para evitar reutilizar tus simulacros, o para cargar simulaciones cuando quieras el módulo requirejs real.

En tu caso, se vería así:

(function () { var stubs = { hurp: ''hurp'', durp: ''durp'' }; var context = createContext(stubs); context([''yourModuleName''], function (yourModule) { //your normal jasmine test starts here describe("yourModuleName", function () { it(''should log'', function(){ spyOn(console, ''log''); yourModule.foo(); expect(console.log).toHasBeenCalledWith(''hurp''); }) }); }); })();

Así que estoy usando este enfoque en producción por un tiempo y es realmente robusto.


Esta respuesta se basa en la respuesta de Andreas Köberle .
No fue tan fácil para mí implementar y comprender su solución, así que lo explicaré con más detalle cómo funciona, y algunos escollos para evitar, con la esperanza de que ayudará a los futuros visitantes.

Entonces, antes que nada, la configuración:
Estoy usando Karma como test runner y MochaJs como framework de prueba.

Usar algo como Squire no funcionó para mí, por alguna razón, cuando lo usé, el marco de prueba arrojó errores:

TypeError: no se puede leer la propiedad ''call'' de undefined

RequireJs tiene la posibilidad de http://requirejs.org/docs/api.html#config-map identificaciones de módulo a otros identificadores de módulo. También permite crear una función require que utiliza una configuración diferente a la require global.
Estas características son cruciales para que esta solución funcione.

Aquí está mi versión del código simulado, que incluye (mucho) comentarios (espero que sea comprensible). Lo envolví dentro de un módulo, para que las pruebas lo puedan requerir fácilmente.

define([], function () { var count = 0; var requireJsMock= Object.create(null); requireJsMock.createMockRequire = function (mocks) { //mocks is an object with the module ids/paths as keys, and the module as value count++; var map = {}; //register the mocks with unique names, and create a mapping from the mocked module id to the mock module id //this will cause RequireJs to load the mock module instead of the real one for (property in mocks) { if (mocks.hasOwnProperty(property)) { var moduleId = property; //the object property is the module id var module = mocks[property]; //the value is the mock var stubId = ''stub'' + moduleId + count; //create a unique name to register the module map[moduleId] = stubId; //add to the mapping //register the mock with the unique id, so that RequireJs can actually call it define(stubId, function () { return module; }); } } var defaultContext = requirejs.s.contexts._.config; var requireMockContext = { baseUrl: defaultContext.baseUrl }; //use the baseUrl of the global RequireJs config, so that it doesn''t have to be repeated here requireMockContext.context = "context_" + count; //use a unique context name, so that the configs dont overlap //use the mapping for all modules requireMockContext.map = { "*": map }; return require.config(requireMockContext); //create a require function that uses the new config }; return requireJsMock; });

El mayor escollo que encontré, que literalmente me costó horas, fue crear la configuración RequireJs. Traté de (profundamente) copiarlo, y solo anulo las propiedades necesarias (como el contexto o el mapa). ¡Esto no funciona! Copie solo la baseUrl , esto funciona bien.

Uso

Para usarlo, createMockRequire en su prueba, cree los createMockRequire y luego páselo a createMockRequire . Por ejemplo:

var ModuleMock = function () { this.method = function () { methodCalled += 1; }; }; var mocks = { "ModuleIdOrPath": ModuleMock } var requireMocks = mocker.createMockRequire(mocks);

Y aquí un ejemplo de un archivo de prueba completo :

define(["chai", "requireJsMock"], function (chai, requireJsMock) { var expect = chai.expect; describe("Module", function () { describe("Method", function () { it("should work", function () { return new Promise(function (resolve, reject) { var handler = { handle: function () { } }; var called = 0; var moduleBMock = function () { this.method = function () { methodCalled += 1; }; }; var mocks = { "ModuleBIdOrPath": moduleBMock } var requireMocks = requireJsMock.createMockRequire(mocks); requireMocks(["js/ModuleA"], function (moduleA) { try { moduleA.method(); //moduleA should call method of moduleBMock expect(called).to.equal(1); resolve(); } catch (e) { reject(e); } }); }); }); }); }); });


Hay una opción config.map http://requirejs.org/docs/api.html#config-map .

Sobre cómo usarlo:

  1. Definir el módulo normal;
  2. Definir módulo de stub;
  3. Configure RequireJS explícitamente;

    requirejs.config({ map: { ''source/js'': { ''foo'': ''normalModule'' }, ''source/test'': { ''foo'': ''stubModule'' } } });

En este caso, para el código normal y de prueba, puede usar el módulo foo , que será la referencia real del módulo y el stub en consecuencia.


He encontrado tres soluciones diferentes a este problema, ninguna de ellas agradable.

Definición de dependencias en línea

define(''hurp'', [], function () { return { beans: ''Beans'' }; }); define(''durp'', [], function () { return { beans: ''durp beans'' }; }); require(''hurpdhurp'', function () { // test hurpdurp in here });

Fugly Tienes que llenar tus pruebas con muchos anuncios repetitivos de AMD.

Cargando falsas dependencias desde diferentes rutas

Esto implica el uso de un archivo config.js separado para definir rutas para cada una de las dependencias que apuntan a los simulacros en lugar de a las dependencias originales. Esto también es feo y requiere la creación de toneladas de archivos de prueba y archivos de configuraciones.

Fingirlo en nodo

Esta es mi solución actual, pero aún es terrible.

Usted crea su propia función de define para proporcionar sus propios simulacros al módulo y coloca sus pruebas en la devolución de llamada. Luego eval el módulo para ejecutar sus pruebas, de esta forma:

var fs = require(''fs'') , hurp = { beans: ''BEANS'' } , durp = { beans: ''durp beans'' } , hurpDurp = fs.readFileSync(''path/to/hurpDurp'', ''utf8''); ; function define(deps, cb) { var TestableHurpDurp = cb(hurp, durp); // now run tests below on TestableHurpDurp, which is using your // passed-in mocks as dependencies. } // evaluate the AMD module, running your mocked define function and your tests. eval(hurpDurp);

Esta es mi solución preferida. Parece un poco mágico, pero tiene algunos beneficios.

  1. Ejecute sus pruebas en el nodo, así que no juegue con la automatización del navegador.
  2. Menos necesidad de repetición AMD en sus pruebas.
  3. Puedes usar eval con ira, e imagina a Crockford explotando de rabia.

Todavía tiene algunos inconvenientes, obviamente.

  1. Como está probando en un nodo, no puede hacer nada con los eventos del navegador o la manipulación DOM. Solo es bueno para probar la lógica.
  2. Todavía un poco torpe para configurar. Necesitas simular define en cada prueba, ya que es donde realmente se ejecutan tus pruebas.

Estoy trabajando en un corredor de pruebas para ofrecer una sintaxis más agradable para este tipo de cosas, pero todavía no tengo una buena solución para el problema 1.

Conclusión

La burla de deps en requirejs es una mierda. Encontré una manera en la que funciona, pero aún no estoy muy contento con ella. Por favor, hágame saber si tiene alguna idea mejor.



es posible que desee comprobar la nueva Squire.js

de los documentos:

¡Squire.js es un inyector de dependencia para usuarios de Require.js para facilitar las dependencias de burla!


si quieres hacer algunas pruebas js simples que aíslan una unidad, entonces simplemente puedes usar este fragmento:

function define(args, func){ if(!args.length){ throw new Error("please stick to the require.js api which wants a: define([''mydependency''], function(){})"); } var fileName = document.scripts[document.scripts.length-1].src; // get rid of the url and path elements fileName = fileName.split("/"); fileName = fileName[fileName.length-1]; // get rid of the file ending fileName = fileName.split("."); fileName = fileName[0]; window[fileName] = func; return func; } window.define = define;