Azure Functions pone a nuestra disposición una forma maravillosa de ejecutar tareas de forma manual o desatendida en un entorno cloud y serverless. Podemos programar tareas de todo tipo en respuesta a multitud de eventos, ya sea una llamada Http, un temporizador o la subida de un documento a un storage, por poner algunos ejemplos.
Desde el momento en el que estamos generando código para nuestras Functions, nos interesa acompañar este código de unos tests que nos aseguren que lo que estamos desarrollando funciona correctamente y que nos protejan de futuras regresiones a la hora de ampliar alguna funcionalidad. En este artículo os mostraremos cómo testear nuestras Azure Functions usando xUnit y Moq en un proyecto de Functions, realizando acciones sobre una cuenta de almacenamiento con blobs en Azure.
Tendréis todo el código subido en este repositorio de GitHub, separado en ramas para que sea más cómodo de seguir según vamos avanzando en el artículo.
Desde la rama “step-0-start” tenemos un proyecto de Functions listo para empezar a hacer tests sobre Azure Functions.
Por una parte, tenemos un BlobStorageService que nos ayudará a realizar acciones con nuestra cuenta de almacenamiento y nuestros blobs.
Y tenemos configurado este servicio en nuestra inyección de dependencias. Esto es importante (al margen de ser una buena práctica) para poder mockear este servicio usando la librería Moq en nuestros tests.
Por otra parte, tenemos un proyecto de Azure Functions con dos funciones:
Vamos a añadir una nueva carpeta dentro de la solución llamada “04-Tests” y ahí añadiremos un nuevo proyecto de tests xUnit que llamaremos “TestingAzureFunctions.Tests”.
Después, añadiremos sobre este proyecto el paquete NuGet de Moq
Y añadimos las referencias a nuestros otros proyectos para poder usarlos en nuestros tests.
Ahora podemos eliminar la clase por defecto que nos viene en el proyecto y añadir dos nuevas clases para testear nuestras dos Functions: “GetBlobTests” y “GetBlobsListTests”.
Y, por último, vamos a añadir una factoría de tests, para poder hacer comprobaciones sobre los logs que arrojan nuestras Functions y mockear requests (tenéis el paso a paso en la documentación oficial de Microsoft y el código ya añadido en el repositorio de ejemplo del artículo).
Con todo esto ya estamos listos para escribir nuestros tests y pasar a la rama “step-1-writtingTests”.
using Azure.Storage.Blobs;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using TestingAzureFunctions.Fnt.Functions;
using TestingAzureFunctions.Services.Abstract;
using Xunit;
namespace TestingAzureFunctions.Tests
{
public class GetBlobsListTests
{
[Fact]
public async Task GetBlobsList_withData_returnArrayOfBlobNames()
{
var listLogger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
var req = TestFactory.CreateHttpRequest(«mockRequest», «mockRequest»);
var mockBlobService = GetMockBlobService(true);
var fnt = new GetBlobsList(mockBlobService);
var response = (OkObjectResult)await fnt.Run(req, listLogger);
string[] blobNames = response.Value as string[];
Assert.NotNull(response);
Assert.Equal(200, response.StatusCode);
Assert.NotEmpty(blobNames);
Assert.Equal(5, blobNames.Length);
Assert.Contains(«Blob list retieved with 5 blob names», listLogger.Logs[2]);
}
[Fact]
public async Task GetBlobsList_withoutData_returnEmptyArray()
{
var listLogger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
var req = TestFactory.CreateHttpRequest(«mockRequest», «mockRequest»);
var mockBlobService = GetMockBlobService(false);
var fnt = new GetBlobsList(mockBlobService);
var response = (OkObjectResult)await fnt.Run(req, listLogger);
string[] blobNames = response.Value as string[];
Assert.NotNull(response);
Assert.Equal(200, response.StatusCode);
Assert.Empty(blobNames);
Assert.Contains(«There are no blobs in this container», listLogger.Logs[2]);
}
[Fact]
public async Task GetBlobsList_raiseException_returnExceptionResult()
{
var listLogger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
var req = TestFactory.CreateHttpRequest(«mockRequest», «mockRequest»);
var mockBlobService = new Mock<IBlobStorageService>();
mockBlobService.Setup(x => x.GetBlobsListAsync(It.IsAny<BlobContainerClient>())).Throws(new Exception());
var fnt = new GetBlobsList(mockBlobService.Object);
var response = await fnt.Run(req, listLogger);
Assert.Contains(«Error getting the blob list», listLogger.Logs.Last());
}
#region Private methods
private IBlobStorageService GetMockBlobService(bool withData)
{
var mockBlobService = new Mock<IBlobStorageService>();
mockBlobService.Setup(x => x.GetBlobsListAsync(It.IsAny<BlobContainerClient>())).Returns(Task.FromResult(GetMockBlobList(withData)));
return mockBlobService.Object;
}
private List<string> GetMockBlobList(bool withData)
{
List<string> blobList = new List<string>();
if (withData)
{
blobList.Add(«doc1.txt»);
blobList.Add(«doc2.txt»);
blobList.Add(«doc3.txt»);
blobList.Add(«doc4.txt»);
blobList.Add(«doc5.txt»);
}
return blobList;
}
#endregion
}
}
Para hacer nuestros test necesitamos un método que nos devuelve una lista de nombres de blobs “GetMockBlobList”, y un método que nos prepare un mock de nuestro IBlobStorageService “GetMockBlobService”, para poder probar todos los casos de nuestros tests.
Ahora ya podemos escribir los tests de nuestra function “GetBlobsList” y probar tres casos:
2.Cuando no nos devuelve datos (GetBlobsList_withoutData_returnEmptyArray)
3. Cuando nos salta una excepción (GetBlobsList_raiseException_returnExceptionResult)
Para los nombres de los tests estamos siguiendo esta notación:
QueEstoyProbando_casoDePrueba_resultadoEsperado
using Azure.Storage.Blobs.Models;
using Moq;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TestingAzureFunctions.Fnt.Functions;
using TestingAzureFunctions.Services.Abstract;
using Xunit;
namespace TestingAzureFunctions.Tests
{
public class GetBlobTests
{
[Fact]
public async Task GetBlob_withData_logBlobContent()
{
var listLogger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
var mockBlobService = new Mock();
mockBlobService.Setup(x => x.DownloadBlobAsync()).Returns(Task.FromResult(GetMockBlob()));
var fnt = new GetBlob(mockBlobService.Object);
await fnt.Run(null, listLogger);
Assert.Contains(«Blob content: doc», listLogger.Logs[3]);
Assert.Contains(«blob content readed successfully», listLogger.Logs.Last());
}
[Fact]
public async Task GetBlob_throwException_logError()
{
var listLogger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
var mockBlobService = new Mock();
mockBlobService.Setup(x => x.DownloadBlobAsync()).Throws(new Exception());
var fnt = new GetBlob(mockBlobService.Object);
await fnt.Run(null, listLogger);
Assert.Contains(«Error getting the blob», listLogger.Logs.Last());
}
#region Private methods
private BlobDownloadInfo GetMockBlob()
{
byte[] firstString = Encoding.UTF8.GetBytes(«doc»);
MemoryStream str = new MemoryStream(firstString);
BlobDownloadInfo blob = BlobsModelFactory.BlobDownloadInfo(
DateTime.UtcNow, 0, BlobType.Block, null, null, null, null, null, null,
CopyStatus.Pending, null, LeaseDurationType.Infinite, null, LeaseState.Available,
null, LeaseStatus.Locked, null, null, default, 0, null, false, null, null,
null, 0, null, null, null, str);
return blob;
}
#endregion
}
}
Para hacer los tests de la function “GetBlob” nos vamos a servir de otro método “GetMockBlob” que nos devuelva un objeto “BlobDownloadInfo” para alimentar a nuestro “IBlobStorageService” mockeado.
Y podemos probar los siguientes casos:
2. Cuando nos salta una excepción (GetBlob_throwException_logError)
Ya hemos visto cómo podemos hacer test unitarios para asegurar que nuestro código está protegido contra regresiones y funciona correctamente.
Hemos añadido una factoría de tests que nos ayuda a la hora de escribir nuestras pruebas.
Nos hemos valido de Moq para emular los casos específicos por los que pasa nuestro flujo de ejecución.
Y hemos comprobado que, en cada caso que se puede dar a lo largo de la ejecución de nuestras Azure Functions, el código se comporta exactamente como deseamos.
El código final está en la rama “step-2-finish” del repositorio, para que podáis consultar el resultado final, y ahora solo nos queda echar el respaldo de la silla para atrás y disfrutar de la maravillosa vista de todos nuestros test pasando con toda la lista en verde 😉
Este sitio web utiliza cookies para que tengas la mejor experiencia de usuario. Si continuas navegando, estás dando tu consentimiento para aceptar las cookies y también nuestra política de cookies (esperemos que no te empaches con tanta cookie 😊)