type - text javascript
Mocha: las afirmaciones dentro de la promesa no funcionan (1)
Por lo tanto, soy completamente nuevo en mocha, y promete y me pidieron que escribiera pruebas de integración, que publica datos de una página, obtiene una clave de autenticación y luego ejecuta pruebas en la clave.
Estoy recuperando la clave correctamente, lo cual es confirmado por mi declaración de registro, pero cuando ejecuto mocha, mi prueba dice 0 pasando. Ni siquiera imprime las descripciones de las declaraciones ''describe'' y ''it'', lo que implica que no funcionan dentro de la promesa. ¿Es posible que esto funcione, mientras sigo dando acceso a las aserciones a mi clave de autorización?
require(''should'');
require(''sails'');
var Promise = require("promise");
var request = require(''request'');
describe(''Domain Model'', function(){
var options = { url:''link'',
form:{
username: ''xxxxx@xxxxxx'',
password: ''0000''
},
timeout: 2000
};
//request auth key
var promise = new Promise(function(resolve,reject){
request.post(options, function(error, response, body){
//body contains the auth key, within a string of JSON
resolve(body);
});
});
//this is where I''m getting lost
promise.then(function(data,test){
var token = (JSON.parse(data))["access_token"];
console.log(token);
describe(''Initialize'',function(){
it(''should be an string'', function(){
(token).should.be.type(''string'');
});
});
});
});
Mocha no puede encontrar sus pruebas porque no las está estructurando correctamente. Los bloques it()
deberían ir inmediatamente dentro de los bloques de la describe()
(y los bloques de la describe()
pueden anidarse uno dentro del otro como mejor le parezca). Si quieres usar promesas, las promesas deberían ir dentro de los bloques it()
, no al revés. Entonces, como primer paso, el código actualizado debería verse más o menos así (cortaré algunas partes para abreviar):
describe(''Domain Model'', function(){
var options = { ... };
describe(''Initialize'',function(){
it(''should be an string'', function(){
//request auth key
var promise = new Promise(function(resolve,reject){
// ...
});
promise.then(function(data,test){
var token = (JSON.parse(data))["access_token"];
console.log(token);
(token).should.be.type(''string'');
});
});
});
});
Tenga en cuenta que la promesa ahora está contenida dentro del cuerpo del it()
. Mocha ahora debería poder encontrar tu prueba. Sin embargo, no pasará. Por qué no?
Como probablemente sepa, las promesas son asincrónicas. Probar el código asincrónico usando mocha requiere el uso de la devolución de llamada done
; consulte la guía para obtener más información. Básicamente, esto significa que el bloque it()
debería aceptar un parámetro llamado done
(¡el nombre es importante!). Esta cosa done
es algo que mocha pasa automáticamente - su presencia indica a mocha que esta prueba contiene código asíncrono, y que por lo tanto no ha completado la ejecución hasta que usted lo diga. La forma en que indica que su prueba está hecha es ejecutando done
, porque done
es en realidad una función de devolución de llamada. Debería ejecutar done()
en cualquier punto que su prueba se considere completa, es decir, al final del bloque de código que se supone debe ejecutarse al último, que en su caso es la parte inferior del controlador .then()
de la promesa. Entonces, mejorando nuestra última versión, el código ahora se ve así (una vez más, cortando algunas partes para abreviar):
describe(''Domain Model'', function(){
var options = { ... };
describe(''Initialize'',function(){
it(''should be an string'', function(done){
//request auth key
var promise = new Promise(function(resolve,reject){
// ...
});
promise.then(function(data,test){
var token = (JSON.parse(data))["access_token"];
console.log(token);
(token).should.be.type(''string'');
done();
});
});
});
});
Tenga en cuenta que acabo de agregar el parámetro done
a it()
, y luego lo llamé en la parte inferior de then()
.
En este punto, el código debería funcionar, creo ... No estoy seguro ya que no puedo probarlo. Sin embargo, hay un par de cosas más que podríamos hacer para mejorar esto más.
Primero, cuestiono su uso de promesas aquí. Si tuviera una API para obtener el token de acceso, optaría por que esa API devuelva una promesa porque las promesas son muy convenientes para la persona que llama. Sin embargo, como estoy seguro de que habrás notado, construir promesas puede ser un poco tedioso, y no creo que agregue mucho valor para tu código. Optaría por hacer esto:
describe(''Domain Model'', function(){
var options = { ... };
describe(''Initialize'',function(){
it(''should be an string'', function(done){
//request auth key
request.post(options, function(error, response, body){
//body contains the auth key, within a string of JSON
var token = (JSON.parse(body))["access_token"];
console.log(token);
(token).should.be.type(''string'');
done();
});
});
});
});
¿No es eso mucho más corto y más dulce? El código sigue siendo asincrónico, por lo que aún debe asegurarse de que su bloque it()
acepte una devolución de llamada done
, y debe llamarlo cuando la prueba haya finalizado.
Ahora, si todavía insistes en usar las promesas, entonces hay una cosa más por la que debo advertirte. ¿Qué sucede si hay un error dentro de su código de controlador .then()
? Bueno, según la documentación :
Si el controlador que se llama arroja una excepción, la promesa devuelta por .then se rechaza con esa excepción.
¿Tiene un controlador de rechazo por su promesa? No. Entonces, ¿qué significa eso? Eso significa que el error se tragará en silencio. Su prueba fallará con el Error: timeout of 2000ms exceeded
, lo cual se debe a que nunca se llamó al controlador done
, pero no se mostrará la causa real del error, y se va a quitar el pelo tratando de descubrir qué fue incorrecto.
¿Entonces que puedes hacer? Puede usar el segundo parámetro para .then()
para especificar un manejador de rechazo, y allí puede aprovechar el hecho de que la devolución de llamada done
que mocha pasa a su prueba acepta un argumento de error, por lo que si llama done("something")
, su prueba fallará (que es lo que queremos en este caso), y el "algo" será la razón. Así que esto es lo que se verá en tu caso:
describe(''Domain Model'', function(){
var options = { ... };
describe(''Initialize'',function(){
it(''should be an string'', function(done){
//request auth key
var promise = new Promise(function(resolve,reject){
// ...
});
promise.then(function(data){
var token = (JSON.parse(data))["access_token"];
console.log(token);
(token).should.be.type(''string'');
done();
}, function (err) {
done(err);
});
});
});
});
Sin embargo, podemos hacerlo aún mejor. Considere lo que sucede si se produce un error desde el controlador de rechazo. No es probable, ya que no estás haciendo mucho allí, simplemente llamando done(err)
. Pero, ¿y si sucede? Bueno, más o menos lo mismo: el error se tragará en silencio, la prueba fallará con un error de timeout
no específico y volverás a sacar tu cabello. ¿Hay alguna manera de que podamos hacer que ese error brote y se vuelva a lanzar?
Como cuestión de hecho, existe: Tanto Q como la biblioteca de promesas que está utilizando tienen un controlador alternativo llamado .done()
(que no debe confundirse con la devolución de llamada done
de mocha). Es similar a .then()
, pero con un comportamiento ligeramente diferente en torno a las excepciones no detectadas. De la documentación :
Promesa # hecho (en Rellenado, en Rechazado)
La misma semántica que .entonces, excepto que no devuelve una promesa y las excepciones se vuelven a lanzar para que puedan registrarse (se bloquea la aplicación en entornos que no sean del navegador)
Perfecto, eso es exactamente lo que queremos. Solo asegúrese de entender cuándo debe usar .then()
, frente a cuándo debe usar .done()
. La Q API hace un trabajo fantástico de explicación (y la biblioteca de la promesa que está utilizando tiene un comportamiento similar, lo he probado):
La regla de oro del fin frente al uso en
then
es: o devuelve tu promesa a otra persona, o si la cadena termina contigo,done
para terminarla. Terminar concatch
no es suficiente porque el controladorcatch
puede arrojar un error.
( Nota: .catch()
parece ser específico de Q, pero es bastante similar a la onRejected
llamada onRejected
, que es el segundo parámetro para .then()
. ).
Así que con eso en mente, simplemente reemplace su último .then()
con un .done()
. Cuando usa .done()
, puede omitir el manejador de rechazo y confiar en la biblioteca de promesa para volver a lanzar cualquier expección no controlada, por lo que obtendrá una descripción de error y un seguimiento de pila. Con lo anterior en mente, su código ahora se ve así:
describe(''Domain Model'', function(){
var options = { ... };
describe(''Initialize'',function(){
it(''should be an string'', function(done){
//request auth key
var promise = new Promise(function(resolve,reject){
// ...
});
promise.done(function(data){
var token = (JSON.parse(data))["access_token"];
console.log(token);
(token).should.be.type(''string'');
done();
});
});
});
});
Básicamente, ignorando el ejemplo de código anterior, la única diferencia con la anterior es que estamos usando .done()
lugar de .then()
.
Con suerte, esto cubre la mayor parte de lo que necesita para comenzar. Hay otras cosas que quizás desee considerar, como la posibilidad de recuperar la clave de autenticación dentro de un enlace before()
lugar de un bloque it()
(porque supongo que lo real que está probando no es la recuperación de la clave - eso es solo un requisito previo para probar las cosas que realmente desea probar, por lo que un gancho podría ser más apropiado - vea aquí ). También me pregunto si deberías o no conectarte a un sistema externo desde dentro de tus pruebas, en vez de simplemente apagarlo (eso depende de si estas son pruebas unitarias o pruebas de integración ). Y estoy seguro de que se puede hacer una mejor afirmación de que solo verificar ese token
es una cadena, como usar una expresión regular para asegurarse de que coincida con un patrón, o realmente probar una solicitud de un recurso protegido y asegurarse de que se transmita . Pero dejaré esas preguntas para que pienses.