Mountebank – Comportamientos

0
347

Aquí tienes la serie completa de tutoriales sobre Mountebank:

Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,3 Ghz Intel Core i7, 16GB DDR4).
  • Sistema Operativo: Mac OS Catalina 10.15.5
  • Entorno de desarrollo: JDK 11, IntelliJ, Docker, Postman

 

Introducción

En este artículo vamos a ver el uso de los comportamientos en Mountebank, con ellos podemos alterar la respuesta, espero que os guste y/o sirva de ayuda en vuestros tests.

Este artículo está dividido en 10 partes:

  • ¿Qué son los comportamientos?
  • Características de los comportamientos
  • Tipos de comportamientos
  • Uso del comportamiento decorate
  • Uso del comportamiento shellTransform
  • Uso del comportamiento wait
  • Uso del comportamiento repeat
  • Uso del comportamiento copy
  • Uso del comportamiento lookup
  • Conclusiones

 

¿Qué son los comportamientos?

Los ingenieros de software que provienen de la escuela de pensamiento orientada a objetos utilizan el término «decorar» para referirse a interceptar un mensaje sencillo y aumentarlo de alguna manera antes de reenviarlo al destinatario. En Mountebank, los comportamientos representan una forma de decorar las respuestas antes de que el impostor las envíe de vuelta.

 

Características de los comportamientos

Los comportamientos se sitúan junto al tipo de respuesta en la definición del stub, se pueden combinar múltiples comportamientos juntos, pero sólo uno de cada tipo. 

Ningún comportamiento debe depender del orden de ejecución de otros comportamientos. 

Algunos comportamientos requieren que el indicador permitirInyección se establezca al iniciar mb. 

Los comportamientos no tienen acceso a ningún estado controlado por el usuario como la inyección de respuesta y no permiten respuestas asíncronas.

Los comportamientos decorate y shellTransform son los únicos que aceptan el objeto de respuesta como entrada y lo transforman de alguna manera, enviando un nuevo objeto de respuesta como salida.

Los comportamientos son agnósticos al tipo de respuesta a la que se aplican, lo que significa que también pueden decorar una respuesta de tipo proxy. Pero por defecto, la decoración se aplica sólo a la respuesta proxy en sí, no a la respuesta que guarda.

{
    "responses": [{
        "proxy": {
            "to": "http://downstream­service.com",
            "mode": "proxyOnce",
            "addDecorateBehavior": "..."
        }
    }]
}

El uso básico de los comportamientos se puede ver claramente en el siguiente ejemplo. La respuesta is primero fusionará el código de estado 500 con la respuesta predeterminada, y luego pasará el objeto de respuesta generado a los comportamientos de decorate y wait

{
    "responses": [{
        "is": { "statusCode": 500 },
        "_behaviors": {
            "decorate": ...,
            "wait": …
        }
    }]
}

 

Tipos de comportamientos

Comportamiento ¿Funciona con las respuestas proxy guardadas? ¿Necesita soporte de inyección? Descripción
decorate yes yes Utiliza una función JavaScript para post procesar la respuesta.
shellTransform no yes Envía la respuesta a través de un pipeline de línea de comandos para el postprocesamiento.
wait yes no Añade latencia a una respuesta
repeat no no Repite una respuesta varias veces
copy no no Copia un valor de la solicitud en la respuesta
lookup no no Reemplaza los datos de la respuesta con datos de una fuente de datos externa basada en una clave de la solicitud.

 

Uso del comportamiento decorate

A menudo nos encontramos con la necesidad de utilizar campos con valores dinámicos en las respuestas de nuestros impostores. Sin comportamientos, nos veríamos obligados a utilizar una respuesta de inyección si un campo de la respuesta fuera dinámico. Por ejemplo, supongamos que deseamos devolver el siguiente cuerpo de respuesta:

{
    "timestamp": "2017­07­22T14:49:21.485Z",
    "givenName": "Stubby",
    "surname": "McStubble",
    "birthDate": "1980­01­01"
}

Como podemos observar en el ejemplo el campo timestamp podría quedar obsoleto rápidamente si necesitáramos que siempre fuera una fecha futura y no muy lejana en el tiempo. Desafortunadamente, traducir eso a una respuesta de tipo inject oculta la intención, como se muestra en el siguiente ejemplo.

{
    "responses": [{
    "inject": "function () { return { body: { timestamp: new Date(), givenName: 'Stubby', surname: 'McStubble', birthDate:'1980­01­01' } } }"
    }]
}

Para entender lo que la respuesta está haciendo, tienes que extraer la función JavaScript y mirarla fijamente. Si comparamos eso con la combinación de una respuesta de tipo is y un comportamiento decorate, que envía el mismo JSON por red pero sin traducciones incómodas, como puedes ver en el siguiente ejemplo, podemos ver que el comportamiento decorate nos aporta mucha más claridad en nuestros impostores:

"responses": [{
    "is": {
        "body": {
            "givenName": "Stubby",
            "surname": "McStubble",
            "birthDate": "1980­01­01"
        }
    },
    "_behaviors": {
        "decorate": "function (config) { config.response.body.timestamp = new Date(); }"
    }
}]

 

Uso del comportamiento shellTransform

El siguiente comportamiento es tanto el más general como el más poderoso, y ambos aspectos vienen con una complejidad añadida. Al igual que decorate, shellTransform le permite el postprocesamiento programático de la respuesta. Pero no requiere el uso de JavaScript y permite encadenar una serie de transformaciones posteriores.

Para ver cómo funciona, tomemos dos transformaciones (añadiendo un % y activando una excepción de límite de tasa) que convertimos a comportamientos shellTransform. Cada transformación se implementa como una aplicación de línea de comandos que acepta la petición y la respuesta codificadas JSON como parámetros en la entrada estándar y devuelve la respuesta codificada JSON transformada en la salida estándar. Comencemos por la configuración del impostor:

"responses": [{
    "is": {
        "headers": {
            "x­rate­limit­remaining": 3
        },
        "body": {
            "givenName": "Stubby",
            "surname": "McStubble",
            "birthDate": "1980­01­01"
        }
    },
    "_behaviors": {
        "shellTransform": [
            "ruby scripts/applyRateLimit.rb",
            "ruby scripts/addTimestamp.rb"
        ]
    }
}]

En este ejemplo, se ha elegido canalizar las transformaciones a través de scripts Ruby, pero podría haber sido cualquier lenguaje. A continuación veremos el código de ambas funciones javascript

applyRateLimit.rb:
require 'json'
response = JSON.parse(ARGV[1])
headers = response['headers']
current_value = headers['x­rate­limit­remaining'].to_i

if File.exists?('rate­limit.txt')
    current_value = File.read('rate­limit.txt').to_i
end

if current_value <= 0
    response['statusCode'] = 429
    response['body'] = {
        'errors' => [{
            'code' => 88, 'message' => 'Rate limit exceeded'
        }]
    }
    response['headers']['x­rate­limit­remaining'] = 0
Else
    File.write('rate­limit.txt', current_value ­ 25)
    headers['x­rate­limit­remaining'] = current_value ­ 25
end
puts response.to_json
addTimestamp.rb:
require 'json'
response = JSON.parse(ARGV[1])
response['body']['timestamp'] = Time.now.getutc
puts response.to_json

Al encadenar transformaciones, shellTransform actúa como una forma de agregar una tubería de transformación a su manejo de respuestas, permitiéndonos agregar toda la complejidad que necesitemos.

 

Uso del comportamiento wait

A veces es necesario simular latencia en las respuestas, y el comportamiento wait le dice a mountebank que se tome un tiempo antes de devolver la respuesta, se le pasa el número de milisegundos de la siguiente manera. Al igual que el comportamiento de decorate, puede agregar el comportamiento de espera a las respuestas guardadas que generan los proxys.

"responses": [{
    "is": {
        "body": {
            "givenName": "Stubby",
            "surname": "McStubble",
            "birthDate": "1980­01­01"
        }
    },
    "_behaviors": {
        "wait": 3000
    }
}]

 

Uso del comportamiento repeat

A veces es necesario enviar la misma respuesta varias veces antes de pasar a la siguiente respuesta. El comportamiento repeat acepta el número de veces que quieres repetir la respuesta.

Un caso de uso común implicaría desencadenar una respuesta de error después de un número determinado de respuestas de “happy path”. Esto se puede hacer con sólo dos respuestas y un comportamiento repetitivo, como se muestra en el ejemplo:

"responses": [{
    "is": {
        "body": {
            "givenName": "Stubby",
            "surname": "McStubble",
            "birthDate": "1980­01­01"
        }
    },
    "_behaviors": {
        "repeat": 3
    }
},
{
    "is": {
        "body": {
            "givenName": "Jerry",
            "surname": "McCorrey",
            "birthDate": "1998­06­06"
        }
    }
}]

 

Uso del comportamiento copy

Siempre se puede agregar datos dinámicos a una respuesta a través de una respuesta de inject, o a través de los comportamientos decorate y shellTransform. Pero estos dos comportamientos adicionales apoyan la inserción de ciertos tipos de datos dinámicos en la respuesta sin la sobrecarga del control programático.

El comportamiento de copy nos permite capturar alguna parte de la solicitud e insertarla en la respuesta, por ejemplo: copiar el id de la solicitud a la respuesta, el comportamiento de copia acepta una matriz, lo que significa que puede realizar múltiples reemplazos en la respuesta. Cada reemplazo debe usar un token diferente, y cada uno puede seleccionar de una parte diferente de la solicitud. El comportamiento de copia soporta: xpath y jsonpath

"responses": [{
    "is": {
        "statusCode": 200,
        "body": {
            "id": "${ID}[1]",
            "givenName": "Stubby",
            "surname": "McStubble",
            "birthDate": "1980­01­01"
        }
    },
    "_behaviors": {
        "copy": [{
            "from": "path",
            "into": "${ID}",
            "using": {
                "method": "regex",
                "selector": "/copy/(.*)"
            }
        }]
    }
}]

 

Uso del comportamiento lookup

El comportamiento de búsqueda le permite reemplazar los tokens en la respuesta con datos dinámicos que provienen de una fuente de datos externa. La única fuente de datos que mountebank soporta es un archivo CSV. Una búsqueda exitosa requiere tres valores:

  1. Una llave seleccionada de la solicitud (Kip Brady)
  2. La conexión a la fuente de datos externa (data/errors.csv)
  3. La columna clave en la fuente de datos externa (nombre)
name,statusCode,errorCode,errorMessage
Tom Larsen,500,serverError,An unexpected error occurred
Kip Brady,400,duplicateEntry,User already exists
Mary Reynolds,400,tooYoung,You must be 18 years old to register
Harry Smith,503,serverBusy,Server currently unavailable
"responses": [{
    "is": {
        "statusCode": "${row}['statusCode']",
        "body": {
            "code": "${row}['errorCode']",
            "message": "${row}['errorMessage']"
        }
    },
    "_behaviors": {
        "lookup": [{
            "key": {
                "from": "path",
                "using": { "method": "regex", "selector": "/lookup/(.*)$" },
                "index": 1
            },
            "fromDataSource": {
                "csv": {
                "path": "/mountebank/servers/data/errors.csv",
                "keyColumn": "name",
                "delimiter": ","
            }
        },
        "into": "${row}"
    }]
  }
}

 

Conclusiones

En este artículo hemos repasado qué son y cómo podemos utilizar los diferentes tipos de comportamientos que provee Mountebank, para sacar mayor provecho a las respuestas de nuestros impostores.

Puedes descargar el proyecto completo aquí.

 

Referencias

http://www.mbtest.org/

https://github.com/bbyars/mountebank

https://github.com/bbyars/mountebank-in-action

https://hub.docker.com/r/bbyars/mountebank

http://www.mbtest.org/docs/api/behaviors

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad