2011-12-15 16 views
22

Estoy tratando de mejorar en la prueba unitaria de mi JavaScript. Tengo el siguiente código:¿Suprimir una llamada al selector jQuery?

var categoryVal = $('#category').val(); 
if (categoryVal === '') { 
    doSomething(); 
} 

Mi corredor de prueba no tiene la entrada #category en la página, así que ¿cómo iba yo a stub/maqueta a cabo el selector de jQuery aquí? He visto tanto la documentación de jasmin como la de sinon, pero no puedo averiguar cómo hacer que funcionen aquí, ya que sus stubs operan en objetos, lo que no es $.

+2

No he utilizado ninguna de esas bibliotecas, pero '' $ es un objeto de hecho (funciones son objetos en JavaScript). Aparte de eso, las funciones de jQuery funcionan cuando no se selecciona nada: '.val()' simplemente devolverá 'indefinido'. – pimvdb

+0

Me sale que '.val()' devolverá 'undefined', pero ¿cómo puedo resguardar eso para probar mi' === '' '? – swilliams

Respuesta

33

El problema aquí es que $() es una función que devuelve un objeto con el método val(). Por lo tanto, debe anotar $() para devolver un objeto trozado que tenga el método val.

$ = sinon.stub(); 
$.withArgs('#category').returns(sinon.stub({val: function(){}})); 

Pero el principal error aquí es dejar que el código que desea probar llamar a la función $() para crear nuevas instancias. ¿Por qué? Su mejor práctica es no crear nuevas instancias en su clase, sino pasarlas al constructor. Digamos que usted tiene la función que va a obtener un valor de una entrada, doblar, y que escribe de nuevo a otro:

function doubleIt(){ 
    $('#el2').val(('#el1').val() *2); 
} 

En este caso se crean 2 nuevos objetos llamando $(). Ahora tiene que anotarse $() para devolver un simulacro y un talón. Utilizando el siguiente ejemplo se puede evitar esto:

function doubleIt(el1, el2){ 
    el2.val(el1.val() *2); 
} 

Mientras que, en el primer caso hay que stub $ para volver a trozo, en el segundo caso se puede pasar fácilmente un talón y un espía en su función.

lo que la prueba sinon para el segundo sería el siguiente:

var el1 = sinon.stub({val: function(){}}); 
    el1.returns(2); 

var el2 = sinon.spy({val: function(){}}, 'val') 

doubleIt(el1, el2) 

assert(el2.withArgs(4).calledOnce) 

Así que, como usted no tiene elementos DOM aquí, sólo tiene que poner a prueba su lógica de la aplicación sin necesidad de crear el mismo dom como en tu aplicación

+2

Terminé haciendo algo como esto. Refactoré todas las manipulaciones de DOM en funciones separadas y anulé las llamadas a esas funciones. Tiene el beneficio adicional de mantener mis métodos extra esbeltos. – swilliams

+1

Gracias por la explicación detallada del problema. Tengo este problema en el nodo js, ​​pero aparece un error cuando intento asignarle $ diciendo que $ no está definido. Me imagino que esto es del modo estricto. ¿Alguna idea de como solucionar esto? –

+0

¿No debería haber un global frente al $ en caso de IIFE? – Jackie

1

Aquí hay una guía bastante buena para probar sus vistas si está utilizando Backbone.js y Jasmin. Desplázate hacia abajo a la sección Ver.

http://tinnedfruit.com/2011/04/26/testing-backbone-apps-with-jasmine-sinon-3.html

Es cierto que los talones operan sobre los objetos. Supongo que el objetivo de crear un talón de vista así.

this.todoViewStub = sinon.stub(window, "TodoView") 
     .returns(this.todoView); 

Solo para poder ver más adelante la vista.

this.view.render(); 

En otras palabras, añada el '#category' div al DOM del TestRunner, de manera que $ puede actuar sobre él. Si su div '#category' no está en this.view, entonces probablemente solo pueda crear una página test.html en la que ejecute su prueba aislada. Este es un patrón común en el marco de JavaScript MVC que estoy más acostumbrado a ese Backbone.

Aquí está un ejemplo simple estructura de la aplicación JMVC:

/todo 
    /models 
     todo.js 
    /list 
     /views 
     init.tmpl 
     listItem.tmpl 
     list.css   
     list.js  (Controller) 
     unitTest.js (Tests for your list.) 
     list_test.html (A html for your unit tests to run on.) 

Tener esta configuración sólo podría incluir el div "#category" en su list_test.html si usted no tiene ya que sucede que tiene dentro de una de los puntos de vista.

+0

Bien, gracias por el enlace ;-) –

9

jQuery usa el motor selector css Sizzle debajo del capó y se desacopló por lo que solo hay unos pocos lugares donde se engancha. Puede interceptar esto para evitar cualquier interacción con el dom.

jQuery.find es el más importante para modificar para responder con cualquier cosa que desee. Sinon podría usarse aquí o intercambiar temporalmente la función.

por ejemplo

existingEngine = jQuery.find 
jQuery.find = function(selector){ console.log(selector) } 
$(".test") 
//>> ".test" 
jQuery.find = existingEngine 

también se puede aplicar una condición de captura específico con un repliegue

existingEngine = jQuery.find 
jQuery.find = function(selector){ 
    if(selector=='blah'}{ return "test"; } 
    return existingEngine.find.apply(existingEngine, arguments) 
} 

En mi trabajo reciente que he hecho un objeto ficticio que responde como un nodo DOM y envuelto de que en un objeto jQuery. Esto responderá a val() correctamente y tendrá todos los métodos jquery presentes que espera. En mi caso, simplemente estoy extrayendo valores de una forma. Si está haciendo una manipulación real, es posible que necesite ser más inteligente que esto, tal vez creando un nodo dom temporal con jQuery que represente lo que esperaba.

obj = { 
    value: "blah", 
    type: "text", 
    nodeName: "input", 
} 
$(obj).val(); // "blah" 
+0

Esta es la mejor respuesta, ¡gracias! @tissak –

+0

Esto está muy unido a las partes internas de jQuery. La prueba de la unidad se romperá si jQuery cambia la forma en que usa el chisporroteo o deja de usarlo. La prueba unitaria solo supone romper si hay un error en el código bajo prueba. – Phil