leanmind logo leanmind text logo

Blog

BDD

Behaviour-driven Development es una técnica para tomar mejores requisitos de producto.

Como nos ayudó saber lo que era el modelo TCP/IP para un escenario de un test en el frontend

Por Jorge Aguiar Martín

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.

Modelo TCP/IP

El modelo TCP/IP viene a definir las distintas capas que tenemos dentro de una comunicación de red.

tcp-ip

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.

Volviendo a lo que íbamos…

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.

Publicado el 20/03/2024 por
Jorge image

Jorge Aguiar Martín

¿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?

Impulsamos el crecimiento profesional de tu equipo de developers