Haciendo tests sobre un servicio que por debajo usaba como cliente HTTP xhr
y no fetch
, se dió la necesidad de mockear esa petición xhr
para poder reproducir los escenarios sin tener que levantar el backend. Como parte curiosa, usamos xhr
por la necesidad de tener actualizaciones constantes del estado de la petición mientras está en curso, y no únicamente cuando ésta termina, cosa para la que fetch
no esta preparada.
Además, el código que hacía la llamada HTTP con xhr
era heredado, por lo que a pesar de que teníamos claro lo que hacía, no teníamos en mente todos los escenarios que estaban cubiertos, de ahí la necesidad de hacer tests sobre el mismo y la decision de mockear el objeto XmlHttpRequest
, en vez de mockear el método correspondiente a esa llamada en nuestra clase Client
, que es donde tenemos encapsuladas todas las llamadas HTTP.
En líneas generales y por seguir aportando luz al contexto de la situación, la parte concreta que estamos tratando en el código, es una petición que realiza una subida de una parte de un fichero, es decir, un chunk. La función es la siguiente:
async pushChunk(
resumableUploadChunk: ResumableUploadChunk,
data: ArrayBuffer,
onprogress: (done: number) => void,
cancellable: Cancellable,
uri: string
): Promise<void> {
return await new Promise((resolve, reject) => {
const xhr = this.createNewRequest()
let curTimeout: any = null
let abortReason: Error = new UploadCanceled()
xhr.upload.onprogress = (event: ProgressEvent) => {
clearTimeout(curTimeout)
const withinProgressTimeout: number = 2000
curTimeout = setTimeout(() => {
abortReason = new ClientError(null, {
message: 'request timeout',
status: 400,
description: 'request timeout'
})
xhr.abort()
}, withinProgressTimeout)
onprogress(event.loaded)
}
xhr.onabort = () => {
clearTimeout(curTimeout)
reject(abortReason)
}
xhr.onerror = () => {
clearTimeout(curTimeout)
reject(new ClientError(null, {
message: 'request error',
status: xhr.status,
description: 'request error'
}))
}
xhr.onload = () => {
clearTimeout(curTimeout)
if (xhr.status !== 200 && xhr.status !== 308) {
reject(new ClientError(null, xhr.response))
}
resolve()
}
xhr.open('PUT', uri, true)
xhr.setRequestHeader('Content-Range', `bytes ${resumableUploadChunk.chunkFirstByte}-${resumableUploadChunk.chunkLastByte}/${resumableUploadChunk.totalObjectSize}`)
xhr.setRequestHeader('Content-Type', 'application/octet-stream')
xhr.withCredentials = true
xhr.send(data)
cancellable.addOnCancel(() => {
clearTimeout(curTimeout)
abortReason = new UploadCanceled()
xhr.abort()
})
})
}
Como puedes ver, no es precisamente fácil de entender de buenas a primeras, porque hay muchas cosas que pueden pasar, y según esas cosas, reaccionamos de una manera u otra.
Ahora te preguntarás, que tiene que ver xhr
, que sólo hace peticiones HTTP, con las capas del modelo TCP/IP. Pues en un principio diríamos que poco, hasta que en un caso concreto, la documentación de xhr
te dice lo siguiente:
XMLHttpRequestEventTarget.onerror es una función que se llama cuando la transacción de XMLHttpRequest falla por culpa de un error.
Es importante mencionar que esta función es solo llamada cuando hay un error a nivel de red. Si el error solo existe a nivel de aplicación (un código de error de HTTP), esta función no será llamada.
El modelo TCP/IP viene a definir las distintas capas que tenemos dentro de una comunicación de red.
Veamos cada una de las capas:
Capa Física: Esta capa se encarga de definir cómo se conectarán y se hará la comunicación físicamente entre todos los dispositivos presentes de la red.
Capa de Enlace: Esta capa se encarga del transporte fiable de la información y de la representación de la misma.
Capa de Red: Esta capa se encarga de la conectividad y enrutamiento de la información.
Capa de Transporte: Esta capa se encarga de que los paquetes lleguen, del orden de los mismos, y sin errores.
Capa de Aplicación: Esta capa usa el resto de aplicaciones para dar servicios, por ejemplo, HTTP.
Entonces, volviendo a analizar la documentación de XHR, vemos que nos dice que la función onError sólo será llamada cuando el error no sea de la capa de aplicación. Es decir, primordialmente cuando falle la conexión, que es un escenario distinto a que el backend nos responda con un código de error, ya que en este caso, sería un error de la capa de aplicación.
Gracias a esto, pudimos crear un test con un escenario en el cual simuláramos una perdida de conexión. Esto nos resultó especialmente útil, ya que al tratarse de una subida de ficheros, era especialmente necesario tener esto en mente.
¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.
Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?
Pero espera 🖐 que tenemos un conflicto interno. A nosotros las newsletter nos parecen 💩👎👹 Por eso hemos creado la LEAN LISTA, la primera lista zen, disfrutona y que suena a rock y reggaeton del sector de la programación. Todos hemos recibido newsletters por encima de nuestras posibilidades 😅 por eso este es el compromiso de la Lean Lista