# Eenheidstest Umami.Net -Testing UmamiClient

# Inleiding

Nu heb ik het [Umami.Net pakket](https://www.nuget.org/packages/Umami.Net/) Daar wil ik natuurlijk zorgen dat het allemaal werkt zoals verwacht. Om dit te doen, is de beste manier om alle methoden en klasse een beetje grondig te testen.
Let op: Dit is geen 'perfecte aanpak ' typepostM SK4 het is ' precies hoe ik dit nu gedaan heb. `IHttpMessageHandler` Hier kun je een DelegatingMessageHandler aanvallen op een normale HttpClient om dit te doen.

[TOC]

<!--category-- xUnit, Umami -->
<datetime class="hidden">2024-09-01T17:22</datetime>

# Eenheidstest

Eenheidstest gaat over het proces van het testen van individuele eenheden van code om ervoor te zorgen dat ze werken zoals verwacht.

Voor een pakket als Umami.Net is dit nogal ingewikkeld, want het roept een afgeleë client op. `HttpClient` en heeft een `IHostedService` Het maakt het versturen van nieuwe gebeurtenisgegevens zo naadloos mogelijk..

## Om UmamiClient te testen

Het grootste deel van het testen `HttpClient` Based library vermijdt de daadwerkelijke 'HttpClient' roepM SK2 Dit gebeurt door een `HttpClient` Die gebruikt een `HttpMessageHandler` Dat geeft terug een bekende reactie. Dit gebeurt door het maken van een `HttpClient` Met een `HttpMessageHandler` Dat geeft terug een bekende reactie;, in dit geval ik echo terug de inputreactie en controleer dat het niet gemanipuleerd is door de `UmamiClient`.

```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;
    }
```

Zoals je ziet, maakt dit een `Mock<HttpMessageHandler>` Ik breng dan in. `UmamiClient`.
In deze code koppel ik dit aan onze computer. `IServiceCollection` Opstellingsmethode. Dit voegt alle diensten toe die nodig zijn bij de `UmamiClient` Inbegrip van onze nieuwe `HttpMessageHandler` Dan keert hij de cijfers terug. `IServiceCollection` voor gebruik in de tests.

```csharp
    public static IServiceCollection SetupServiceCollection(string webSiteId = Consts.WebSiteId,
        string umamiPath = Consts.UmamiPath, HttpMessageHandler? handler = null)
    {
        var services = new ServiceCollection();
        var umamiClientSettings = new UmamiClientSettings
        {
            WebsiteId = webSiteId,
            UmamiPath = umamiPath
        };
        services.AddSingleton(umamiClientSettings);
        services.AddScoped<PayloadService>();
        services.AddLogging(x => x.AddConsole());
        // Mocking HttpMessageHandler with Moq
        var mockHandler = handler ?? EchoMockHandler.Create();
        services.AddHttpClient<UmamiClient>((serviceProvider, client) =>
        {
            var umamiSettings = serviceProvider.GetRequiredService<UmamiClientSettings>();
            client.BaseAddress = new Uri(umamiSettings.UmamiPath);
        }).ConfigurePrimaryHttpMessageHandler(() => mockHandler);
        return services;
    }
```

Om dit te gebruiken en in te injecteren. `UmamiClient` Dan gebruik ik deze diensten in de `UmamiClient` opstelling.

```csharp
    public static UmamiClient GetUmamiClient(IServiceCollection? serviceCollection = null,
        HttpContextAccessor? contextAccessor = null)
    {
        serviceCollection ??= SetupServiceCollection();
        SetupUmamiClient(serviceCollection, contextAccessor);
        if (serviceCollection == null) throw new NullReferenceException(nameof(serviceCollection));
        var serviceProvider = serviceCollection.BuildServiceProvider();
        return serviceProvider.GetRequiredService<UmamiClient>();
    }
```

Je zal zien dat ik hier een hoop alternatieve opsionele parameters heb waardoor ik verschillende opties kan injecteren voor verschillende testtypen.

### De tests

Dus nu heb ik al deze opstelling op de plaats Ik kan nu beginnen met het schrijven van toetsen voor `UmamiClient` methodes.

#### Stuur

Dit alles betekent dat onze tests eigenlijk vrij eenvoudig kunnen zijn.

```csharp
public class UmamiClient_SendTests
{
    [Fact]
    public async Task Send_Wrong_Type()
    {
        var umamiClient = SetupExtensions.GetUmamiClient();
        await Assert.ThrowsAsync<ArgumentException>(async () => await umamiClient.Send(type: "boop"));
    }

    [Fact]
    public async Task Send_Empty_Success()
    {
        var umamiClient = SetupExtensions.GetUmamiClient();
        var response = await umamiClient.Send();
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}
```

Hier zie je de eenvoudigste testcase, die ervoor zorgt dat het `UmamiClient` We kunnen een bericht sturen en een antwoord krijgen. `type` is verkeerd. Dit is een vaak over het hoofd gezien onderdeel van het testen, om te verzekeren dat de code niet werkt zoals verwacht.

#### Bladsy Besigtig

Om onze paginabeeldmethode te testen kunnen we iets gelijkaardigs doen. In de code onder gebruik ik mijn `EchoHttpHandler` om gewoon de gestuurde reactie terug te reflecteren en ervoor te zorgen dat het terug stuurt wat ik verwacht.

```csharp
    [Fact]
    public async Task TrackPageView_WithNoUrl()
    {
        var defaultUrl = "/testpath";
        var contextAccessor = SetupExtensions.SetupHttpContextAccessor(path: "/testpath");
        var umamiClient = SetupExtensions.GetUmamiClient(contextAccessor: contextAccessor);
        var response = await umamiClient.TrackPageView();

        var content = await response.Content.ReadFromJsonAsync<EchoedRequest>();
        Assert.NotNull(response);
        Assert.NotNull(content);
        Assert.Equal(content.Payload.Url, defaultUrl);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
```

### HttpContextAccessor

Dit gebruikt de `HttpContextAccessor` om de weg te bepalen naar: `/testpath` Dan controleert hij dat de `UmamiClient` stuurt dit correct.

```csharp
    public static HttpContextAccessor SetupHttpContextAccessor(string host = Consts.Host,
        string path = Consts.Path, string ip = Consts.Ip, string userAgent = Consts.UserAgent,
        string referer = Consts.Referer)
    {
        HttpContext httpContext = new DefaultHttpContext();
        httpContext.Request.Host = new HostString(host);
        httpContext.Request.Path = new PathString(path);
        httpContext.Connection.RemoteIpAddress = IPAddress.Parse(ip);
        httpContext.Request.Headers.UserAgent = userAgent;
        httpContext.Request.Headers.Referer = referer;

        var context = new HttpContextAccessor { HttpContext = httpContext };
        return context;
    }

```

Dit is belangrijk voor onze Umami-kliëntcode omdat veel van de gegevens die uit elke verzoek worden gestuurd, dynamisch wordt geproduceerd vanuit de server. `HttpContext` object. Dus we kunnen niets sturen in een `await umamiClient.TrackPageView();` Het stuurt nog steeds de juiste gegevens door het Url uit te halen. `HttpContext`.

Zoals we later zullen zien, is het ook belangrijk dat de verwondering dingen als deze sturen. `UserAgent` en `IPAddress` Deze worden gebruikt door de Umami server om de gegevens te volgen en 'track' gebruiker aansigten zonder cookies te gebruikenM SK2

Om dit voorspelbaar te hebben, definiëren we een aantal Konten in de `Consts` class. Zo kunnen we testen tegen voorspelbare reacties en verzoeken.

```csharp
public class Consts
{
    public const string UmamiPath = "https://example.com";
    public const string WebSiteId = "B41A9964-FD33-4108-B6EC-9A6B68150763";
    public const string Host = "example.com";
    public const string Path = "/example";
    public const string Ip = "127.0.0.1";
    public const string UserAgent = "Test User Agent";
    public const string Referer = "Test Referer";
    public const string DefaultUrl = "/testpath";
    public const string DefaultTitle = "Example Page";
    public const string DefaultName = "RSS";
    public const string DefaultType = "event";

    public const string Email = "test@test.com";

    public const string UserId = "11224456";
    
    public const string UserName = "Test User";
    
    public const string SessionId = "B41A9964-FD33-4108-B6EC-9A6B68150763";
}
```

## Volgende testen

Dit is slechts het begin van onze teststrategie voor Umami.Net, we moeten nog steeds de `IHostedService` en testen tegen de feitelijke gegevens Umami genereert (, dat niet waar dan ook dokumenteerd is maar een JWT-token bevat met nuttige data.

```json
{
  "alg": "HS256",
  "typ": "JWT"
}{
  "id": "b9836672-feee-55c5-985a-a5a23d4a23ad",
  "websiteId": "32c2aa31-b1ac-44c0-b8f3-ff1f50403bee",
  "hostname": "example.com",
  "browser": "chrome",
  "os": "Windows 10",
  "device": "desktop",
  "screen": "1920x1080",
  "language": "en-US",
  "country": "GB",
  "subdivision1": null,
  "subdivision2": null,
  "city": null,
  "createdAt": "2024-09-01T09:26:14.418Z",
  "visitId": "e7a6542f-671a-5573-ab32-45244474da47",
  "iat": 1725182817
}2|Y*: �(N%-ޘ^1>@V
```

Dus we willen testen voor dat , het token simuleren en mogelijk de gegevens teruggeven bij elke bezoek. `uuid(websiteId,ipaddress, useragent)`).

# In slotting:

Dit is nog maar het begin van het testen van de Umami-package.