En términos generales, Mock, o Imitar según la terminología argentina, consiste en inducir a que un método o servicio responda con información ficticia que no facilite la evaluación de nuestra lógica empresarial durante las pruebas.
Además, también podemos llevar a cabo estas acciones para que otros equipos puedan progresar sin contratiempos. Imaginemos que el equipo encargado de implementar las API no está disponible debido a que está ocupado en otra tarea. De manera sencilla, podremos emular el comportamiento para construir lo necesario sin esperar a que esté lista.
¡Comencemos!
Nos dispondremos a crear un archivo en formato JSON que contenga una estructura con los datos de respuesta. De manera aproximada, exhibirá la siguiente configuración:
{
"Customers":[
{
"Id": 1,
"Name": "Homero Simpsons"
},
{
"Id": 2,
"Name": "Barny Gomez"
},
{
"Id": 3,
"Name": "Moe Sislack"
}
]
}
Llevaremos a cabo GET, POST, PUT y DELETE cumpliendo con todos los aspectos relacionados con la arquitectura RESTful. Esto marca el comienzo a un nivel superior de lo que precisamos y la delineación del comportamiento anticipado de las operaciones.
Ahora vamos a crear en Visual Studio 2022 nuestro proyecto:
No olvides marcar la opción “Habilitar Controladores (desactivar para usar API mínimas)”.
Al explorar el contenido de nuestro programs.cs, nos encontramos con la existencia de app.MapGet, el cual establece que para una ruta específica, al recibir el verbo GET, se generará una respuesta. También existen los demás métodos como MapPost(), MapPut() o MapDelete().
En un primer paso, introduciremos el Método Get. Para configurar las respuestas, es preciso iterar sobre el objeto app, que pertenece al tipo WebApplication. Esto asegura la vinculación de las rutas de configuración con los tipos de respuestas previstos. Incorporamos el siguiente código correspondiente al Get.
var writableDoc = JsonNode.Parse(File.ReadAllText("fake.json"));
foreach(var elem in writableDoc?.Root.AsObject().AsEnumerable()) {
var arr = elem.Value.AsArray();
app.MapGet(string.Format("/{0}", elem.Key), () => elem.Value.ToString());
}
A continuación, nos dirigiremos a nuestro archivo program.cs para incluir:
app.UseExtraRoutes();
Lo activamos al presionar y apreciamos la generación de un método a partir de la estructura definida, con el resultado que se presenta a continuación:
Ahora nos adentramos un paso más al incorporar un Get capaz de realizar filtrados mediante el Id. Regresamos nuevamente a nuestro archivo RouteMiddlewareExtensions.cs y añadimos el siguiente código debajo del anterior Get:
app.MapGet(string.Format("/{0}", elem.Key) + "/{id}", (int id) =>
{
var matchedItem = arr.SingleOrDefault(row => row
.AsObject()
.Any(o => o.Key.ToLower() == "id" && int.Parse(o.Value.ToString()) == id)
);
return matchedItem;
});
Veamos los resultados de la ejecución:
El paso subsiguiente es la incorporación de un post. Para ello, requerimos escribir en nuestro archivo fake.json con la información que deseamos guardar y mantener. Para lograrlo, debemos escribir en el archivo utilizando System.IO para leer la estructura y actualizar nuevamente el archivo. Veamos el código:
app.MapPost(string.Format("/{0}", elem.Key), async (HttpRequest request) => {
string content = string.Empty;
using(StreamReader reader = new StreamReader(request.Body))
{
content = await reader.ReadToEndAsync();
}
var newNode = JsonNode.Parse(content);
var array = elem.Value.AsArray();
newNode.AsObject().Add("Id", array.Count() + 1);
array.Add(newNode);
File.WriteAllText("mock.json", writableDoc.ToString());
return content;
});
Una vez más, accederemos a nuestro archivo RouteMiddlewareExtensions.cs y incluiremos el siguiente código debajo del Get por el id anteriormente mencionado.
Próximos a implementar Delete, que equivale a una actualización. Muy similar al anterior, pero emplearemos Map Delete. Veamos el código que debemos agregar debajo del MapPost:
app.MapDelete(string.Format("/{0}", elem.Key) + "/{id}", (int id) => {
var matchedItem = arr
.Select((value, index) => new{ value, index})
.SingleOrDefault(row => row.value
.AsObject()
.Any(o => o.Key.ToLower() == "id" && int.Parse(o.Value.ToString()) == id)
);
if (matchedItem != null) {
arr.RemoveAt(matchedItem.index);
File.WriteAllText("mock.json", writableDoc.ToString());
}
return "OK";
});
Ahora le asignamos como tarea la creación de PUT para que lo realicen en casa. No debería ser tan desafiante con los ejemplos que hemos revisado hasta ahora. Un consejo, es muy muy análogo al POST. Aquí tienen el código completo:
using System.Text.Json;
using System.Text.Json.Nodes;
namespace mock;
public static class RouteMiddlewareExtensions
{
public static WebApplication UseExtraRoutes(this WebApplication app)
{
var writableDoc = JsonNode.Parse(File.ReadAllText("fake.json"));
foreach (var elem in writableDoc?.Root.AsObject().AsEnumerable())
{
var arr = elem.Value.AsArray();
app.MapGet(string.Format("/{0}", elem.Key), () => elem.Value.ToString());
app.MapGet(string.Format("/{0}", elem.Key) + "/{id}", (int id) =>
{
var matchedItem = arr.SingleOrDefault(row => row
.AsObject()
.Any(o => o.Key.ToLower() == "id" && int.Parse(o.Value.ToString()) == id)
);
return matchedItem;
});
app.MapPost(string.Format("/{0}", elem.Key), async (HttpRequest request) => {
string content = string.Empty;
using (StreamReader reader = new StreamReader(request.Body))
{
content = await reader.ReadToEndAsync();
}
var newNode = JsonNode.Parse(content);
var array = elem.Value.AsArray();
newNode.AsObject().Add("Id", array.Count() + 1);
array.Add(newNode);
File.WriteAllText("mock.json", writableDoc.ToString());
return content;
});
app.MapDelete(string.Format("/{0}", elem.Key) + "/{id}", (int id) => {
var matchedItem = arr
.Select((value, index) => new { value, index })
.SingleOrDefault(row => row.value
.AsObject()
.Any(o => o.Key.ToLower() == "id" && int.Parse(o.Value.ToString()) == id)
);
if (matchedItem != null)
{
arr.RemoveAt(matchedItem.index);
File.WriteAllText("mock.json", writableDoc.ToString());
}
return "OK";
});
}
return app;
}
}
Conclusiones
Esta opción se presenta como una excelente oportunidad para crear API sin depender de la implementación completa de la lógica de negocio. Por lo tanto, resultaría muy conveniente evitar esperar a algún equipo encargado de desarrollarlo, especialmente si conocemos las estructuras que vamos a recuperar. Espero que les resulte beneficioso. Nos reencontramos en futuras publicaciones.