# Unidad de prueba Umami.Net -Testing Umami Data Without Using Moq

# El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

En la parte anterior de esta serie donde he probado[ El Comité recomienda al Estado Parte que incluya en su próximo informe periódico datos estadísticos desglosados por sexo, edad y origen étnico. ](/blog/unittestingumaminet)

<!--category-- xUnit, ASP.NET Core -->
<datetime class="hidden">2024-09-04T20:30</datetime>
[TOC]

## El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

En la parte anterior he utilizado Moq para darme un `Mock<HttpMessageHandler>` y devolver el handler utilizado en `UmamiClient`, este es un patrón común cuando se proba el código que utiliza `HttpClient`. En este post le mostraré cómo probar el nuevo `UmamiDataService` Sin usar Moq.

```csharp
    public static HttpMessageHandler Create()
    {
        var mockHandler = new Mock<HttpMessageHandler>();

        mockHandler.Protected()
            .Setup<Task<HttpResponseMessage>>(
                "SendAsync",
                ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.ToString().Contains("api/send")),
                ItExpr.IsAny<CancellationToken>())
            .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) =>
            {
                // Read the request content
                var requestBody = request.Content != null
                    ? request.Content.ReadAsStringAsync(cancellationToken).Result
                    : null;

                // Create a response that echoes the request body
                var responseContent = requestBody != null
                    ? requestBody
                    : "No request body";


                // Return the response
                return new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new StringContent(responseContent, Encoding.UTF8, "application/json")
                };
            });

        return mockHandler.Object;
    }
```

## Por qué usar Moq?

Moq es una potente biblioteca de mocking que le permite crear objetos de mock para interfaces y clases. Se usa ampliamente en la prueba de unidades para separar el código que se está probando de sus dependencias . Sin embargo, hay algunos casos en los que usar Moq puede ser pesado o incluso imposibleMSC3 Por ejemploM SK4 cuando se está testando código que utiliza métodos estáticos o cuando el código bajo prueba está estrechamente coupledo a sus dependencia.

El ejemplo que he dado anteriormente da mucha flexibilidad en la prueba del `UmamiClient` class, pero también tiene algunas deficiencias. Es el código UGLY de ' y hace muchas cosas que no necesito realmente. `UmamiDataService` Por lo que respecta a la cuestión de los derechos humanos y las libertades fundamentales, mi delegación apoya plenamente el proyecto de resolución A/C.3/57/L.38, en su forma oralmente revisada.

# La Comisión Consultiva en Asuntos Administrativos y de Presupuesto examinó el proyecto de presupuesto por programas para el bienio 2006-2007 (A/60/6 (Sect.

El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `UmamiDataService` es una futura adición a la biblioteca Umami.Net que le permitirá extraer datos de Umami para cosas como ver cuántas visitas tenía una páginaM SK1 qué sucesos se produjeron de un determinado tipo , filtrado por una tonelada de parámetros liek país, ciudadMST4 OSM ST5 tamaño de pantallaMst6 etcM st7 Esto es muy potente pero en estos momentos el [Umami API sólo funciona a través de JavaScript](https://umami.is/docs/api/website-stats). Así que deseando jugar con esos datos, he llevado a cabo el esfuerzo de crear un cliente para él.

El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `UmamiDataService` class se divide en clases parciales múltiples (los métodos son SUPER largosM SK1 por ejemplo aquí' es el `PageViews` El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre hombres y mujeres en materia de empleo, incluida la adopción de medidas legislativas, administrativas, judiciales y de otra índole.

Usted puede ver que MUCH del código está construyendo la QueryString desde el que se ha pasado en la clase PageViewsRequest (hay otras formas de hacerlo pero estaM SK1 por ejemplo usando Attributes o trabajos de reflexión aquí).

<details>
<summary>GetPageViews</summary>

```csharp
    public async Task<UmamiResult<PageViewsResponseModel>> GetPageViews(PageViewsRequest pageViewsRequest)
    {
        if (await authService.LoginAsync() == false)
            return new UmamiResult<PageViewsResponseModel>(HttpStatusCode.Unauthorized, "Failed to login", null);
        // Start building the query string
        var queryParams = new List<string>
        {
            $"startAt={pageViewsRequest.StartAt}",
            $"endAt={pageViewsRequest.EndAt}",
            $"unit={pageViewsRequest.Unit.ToLowerString()}"
        };

        // Add optional parameters if they are not null
        if (!string.IsNullOrEmpty(pageViewsRequest.Timezone)) queryParams.Add($"timezone={pageViewsRequest.Timezone}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Url)) queryParams.Add($"url={pageViewsRequest.Url}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Referrer)) queryParams.Add($"referrer={pageViewsRequest.Referrer}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Title)) queryParams.Add($"title={pageViewsRequest.Title}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Host)) queryParams.Add($"host={pageViewsRequest.Host}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Os)) queryParams.Add($"os={pageViewsRequest.Os}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Browser)) queryParams.Add($"browser={pageViewsRequest.Browser}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Device)) queryParams.Add($"device={pageViewsRequest.Device}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Country)) queryParams.Add($"country={pageViewsRequest.Country}");
        if (!string.IsNullOrEmpty(pageViewsRequest.Region)) queryParams.Add($"region={pageViewsRequest.Region}");
        if (!string.IsNullOrEmpty(pageViewsRequest.City)) queryParams.Add($"city={pageViewsRequest.City}");

        // Combine the query parameters into a query string
        var queryString = string.Join("&", queryParams);

        // Make the HTTP request
        var response = await authService.HttpClient.GetAsync($"/api/websites/{WebsiteId}/pageviews?{queryString}");

        if (response.IsSuccessStatusCode)
        {
            logger.LogInformation("Successfully got page views");
            var content = await response.Content.ReadFromJsonAsync<PageViewsResponseModel>();
            return new UmamiResult<PageViewsResponseModel>(response.StatusCode, response.ReasonPhrase ?? "Success",
                content ?? new PageViewsResponseModel());
        }

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            await authService.LoginAsync();
            return await GetPageViews(pageViewsRequest);
        }

        logger.LogError("Failed to get page views");
        return new UmamiResult<PageViewsResponseModel>(response.StatusCode,
            response.ReasonPhrase ?? "Failed to get page views", null);
    }
```

</details>
Como usted puede ver esto realmente sólo construye una cadena de consulta. autentica el llamamiento (ver la [El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.](/blog/unittestinglogginginaspnetcore) para algunos detalles sobre esto) y luego hace el llamamiento a la API de Umami. Así que ¿cómo testamos esto ?

## La Comisión Consultiva en Asuntos Administrativos y de Presupuesto examinó el proyecto de presupuesto por programas para el bienio 2006-2007 (A/60/6 (Sect.

En contraste con la prueba de UmamiClient, he decidido probar el `UmamiDataService` sin usar Moq. En lugar de, he creado un simple `DelegatingHandler` class que me permite interrogar la solicitud y devolver una respuesta. Este es un enfoque mucho más sencillo que usar Moq y me permite probar el `UmamiDataService` El Comité recomienda al Estado Parte que incluya en su próximo informe periódico información detallada sobre las medidas adoptadas por el Estado parte para eliminar la discriminación contra la mujer. `HttpClient`.

En el código de abajo usted puede ver simplemente ampliar `DelegatingHandler` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `SendAsync` method. Este método me permite inspeccionar la solicitud y devolver una respuesta basada en la solicitud.

```csharp
public class UmamiDataDelegatingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var absPath = request.RequestUri.AbsolutePath;
        switch (absPath)
        {
            case "/api/auth/login":
                var authContent = await request.Content.ReadFromJsonAsync<AuthRequest>(cancellationToken);
                if (authContent?.username == "username" && authContent?.password == "password")
                    return ReturnAuthenticatedMessage();
                else if (authContent?.username == "bad")
                {
                    return new HttpResponseMessage(HttpStatusCode.Unauthorized);
                }
                else
                {
                    return new HttpResponseMessage(HttpStatusCode.BadRequest);
                }
            default:

                if (absPath.StartsWith($"/api/websites/{Consts.WebSiteId}/pageviews"))
                {
                    var pageViews = GetParams<PageViewsRequest> (request);
                  
                    return ReturnPageViewsMessage(pageViews);
                }

                if (absPath.StartsWith($"/api/websites/{Consts.WebSiteId}/metrics"))
                {
                    var metricsRequest = GetParams<MetricsRequest>(request);
                    return ReturnMetrics(metricsRequest);
                }

                return new HttpResponseMessage(HttpStatusCode.NotFound);
        }
    }
 }
```

## El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

En el párrafo 2 de la parte dispositiva del proyecto de resolución, la Asamblea General pidió al Secretario General que le presentara en su quincuagésimo séptimo período de sesiones un informe sobre los progresos realizados en la aplicación de la presente resolución. `UmamiDataService` para usar este handler es similarmente simple.

```csharp
    public IServiceProvider GetServiceProvider (string username="username", string password="password")
    {
        var services = new ServiceCollection();
        var mockLogger = new FakeLogger<UmamiDataService>();
        var authLogger = new FakeLogger<AuthService>();
        services.AddScoped<ILogger<UmamiDataService>>(_ => mockLogger);
        services.AddScoped<ILogger<AuthService>>(_ => authLogger);
        services.SetupUmamiData(username, password);
        return  services.BuildServiceProvider();
        
    }
```

En la misma sesión, el Consejo aprobó el proyecto de resolución A/C.1/60/L.20 sin someterlo a votación (véase párr. `ServiceCollection`El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre hombres y mujeres en materia de empleo, incluida la eliminación del trabajo forzoso. `FakeLogger<T>` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. [el último artículo para detalles sobre esto](/blog/unittestinglogginginaspnetcore) El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `UmamiData` servicio con el nombre de usuario y contraseña que quiero usar (de modo que podo comprobar el fallo).

El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `services.SetupUmamiData(username, password);` que es un método de extensión que he creado para configurar el `UmamiDataService` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `UmamiDataDelegatingHandler` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `AuthService`;

```csharp
    public static void SetupUmamiData(this IServiceCollection services, string username="username", string password="password")
    {
        var umamiSettings = new UmamiDataSettings()
        {
            UmamiPath = Consts.UmamiPath,
            Username = username,
            Password = password,
            WebsiteId = Consts.WebSiteId
        };
        services.AddSingleton(umamiSettings);
        services.AddHttpClient<AuthService>((provider,client) =>
        {
            client.BaseAddress = new Uri(umamiSettings.UmamiPath);
            

        }).AddHttpMessageHandler<UmamiDataDelegatingHandler>()
            .SetHandlerLifetime(TimeSpan.FromMinutes(5));  //Set lifetime to five minutes

        services.AddScoped<UmamiDataDelegatingHandler>();
        services.AddScoped<UmamiDataService>();
    }
```

Usted puede ver que esto es donde me adhiero al `UmamiDataDelegatingHandler` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `AuthService` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `UmamiDataService`El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `AuthService` El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre hombres y mujeres en materia de empleo, incluida la adopción de medidas legislativas, administrativas, judiciales y de otra índole. `HttpClient` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `UmamiDataService` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `AuthService` para hacer las llamadas a la API de Umami con el `bearer` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `BaseAddress` En el párrafo 6 de su resolución 57/270 B, la Asamblea General pidió al Secretario General que le presentara en su quincuagésimo séptimo período de sesiones un informe sobre los progresos realizados en la aplicación del Programa de Acción para prevenir, combatir y eliminar todas las formas de discriminación contra la mujer.

## El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Realmente esto hace realmente probar este realmente sencillo. Es' es un poco verboso como yo también quería probar el registro también . Todo lo que está haciendoM SK3 es publicar a través de mi `DelegatingHandler` y simulo una respuesta basada en la solicitud.

```csharp
public class UmamiData_PageViewsRequest_Test : UmamiDataBase
{
    private readonly DateTime StartDate = DateTime.ParseExact("2021-10-01", "yyyy-MM-dd", null);
    private readonly DateTime EndDate = DateTime.ParseExact("2021-10-07", "yyyy-MM-dd", null);
    
    [Fact]
    public async Task SetupTest_Good()
    {
        var serviceProvider = GetServiceProvider();
        var umamiDataService = serviceProvider.GetRequiredService<UmamiDataService>();
        var authLogger = serviceProvider.GetRequiredService<ILogger<AuthService>>();
        var umamiDataLogger = serviceProvider.GetRequiredService<ILogger<UmamiDataService>>();
        var result = await umamiDataService.GetPageViews(StartDate, EndDate);
        var fakeAuthLogger = (FakeLogger<AuthService>)authLogger;
        FakeLogCollector collector = fakeAuthLogger.Collector; 
        IReadOnlyList<FakeLogRecord> logs = collector.GetSnapshot();
        Assert.Contains("Login successful", logs.Select(x => x.Message));
        
        var fakeUmamiDataLogger = (FakeLogger<UmamiDataService>)umamiDataLogger;
        FakeLogCollector umamiDataCollector = fakeUmamiDataLogger.Collector;
        IReadOnlyList<FakeLogRecord> umamiDataLogs = umamiDataCollector.GetSnapshot();
        Assert.Contains("Successfully got page views", umamiDataLogs.Select(x => x.Message));
        
        Assert.NotNull(result);
    }
}
```

### El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Para simular la respuesta para este método usted' le recordará que tengo esta línea en el `UmamiDataDelegatingHandler`:

```csharp
  if (absPath.StartsWith($"/api/websites/{Consts.WebSiteId}/pageviews"))
                {
                    var pageViews = GetParams<PageViewsRequest> (request);
                  
                    return ReturnPageViewsMessage(pageViews);
                }
```

Todo esto hace es extraer información de la cadena de consulta y construye una respuesta 'realistaM SK1 basada en pruebas de vivo I've compiled , otra vez muy pocas fichas en esta SSK5 UstedMSC6verás que testo para el número de días entre la fecha de inicio y la fecha final y luego devolveré una respuesta con el mismo número de dias

```csharp
    private static HttpResponseMessage ReturnPageViewsMessage(PageViewsRequest request)
    {
        var startAt = request.StartAt;
        var endAt = request.EndAt;
        var startDate = DateTimeOffset.FromUnixTimeMilliseconds(startAt).DateTime;
        var endDate = DateTimeOffset.FromUnixTimeMilliseconds(endAt).DateTime;
        var days = (endDate - startDate).Days;

        var pageViewsList = new List<PageViewsResponseModel.Pageviews>();
        var sessionsList = new List<PageViewsResponseModel.Sessions>();
        for(int i=0; i<days; i++)
        {
            
            pageViewsList.Add(new PageViewsResponseModel.Pageviews()
            {
                x = startDate.AddDays(i).ToString("yyyy-MM-dd"),
                y = i*4
            });
            sessionsList.Add(new PageViewsResponseModel.Sessions()
            {
                x = startDate.AddDays(i).ToString("yyyy-MM-dd"),
                y = i*8
            });
        }
        var pageViewResponse = new PageViewsResponseModel()
        {
            pageviews = pageViewsList.ToArray(),
            sessions = sessionsList.ToArray()
        };
        var json = JsonSerializer.Serialize(pageViewResponse);
        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(json, Encoding.UTF8, "application/json")
        };
    }
```

# El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Así que ' es realmente ' es bastante sencillo probar un `HttpClient` la solicitud sin usar Moq y creo que es' es mucho más limpio de esta manera. pierdes una parte de la sofisticación posible en Moq pero para pruebas simples como ésta , Creo que es