# यूनिट परीक्षण उमामी.नेट - परीक्षण उममी पृष्ठभूमिप्रेषक

# परिचय

पिछले लेख में हम discussed how to test the `UmamiClient` xUnit और Moq का उपयोग करते हुए. इस लेख में हम discuss how to test the `UmamiBackgroundSender` वर्ग. `UmamiBackgroundSender` से थोड़ा अलग है `UmamiClient` जैसे कि यह उपयोग करता है `IHostedService` पृष्ठभूमि में चल रहे और के माध्यम से अनुरोध भेजने के लिए `UmamiClient` मुख्य निष्पादन थ्रेड से पूरी तरह बाहर है (तो यह नहीं करता है

जैसा कि आमतौर पर आप इस के लिए सभी स्रोत कोड मेरी GitHub पर देख सकते हैं [यहाँ](https://github.com/scottgal/mostlylucidweb/blob/main/Umami.Net.Test/UmamiBackgroundSender_Tests.cs).

[TOC]

<!--category-- xUnit, Umami, IHostedService, Moq -->
<datetime class="hidden">2024-09-03T09:00</datetime>

## `UmamiBackgroundSender`

वास्तविक संरचना `UmamiBackgroundSender` काफी सरल है. यह एक होस्ट किया गया सेवा है जो नए अनुरोध को पता लगाने के साथ ही उमामी सर्वर में अनुरोध भेजता है `UmamiBackgroundSender` वर्ग नीचे दिखाया गया है

```csharp
public class UmamiBackgroundSender(IServiceScopeFactory scopeFactory, ILogger<UmamiBackgroundSender> logger) : IHostedService
{

    private  Channel<SendBackgroundPayload> _channel = Channel.CreateUnbounded<SendBackgroundPayload>();

    private Task _sendTask = Task.CompletedTask;
    
        public Task StartAsync(CancellationToken cancellationToken)
    {

        _sendTask = SendRequest(_cancellationTokenSource.Token);
        return Task.CompletedTask;
    }
    
            public async Task StopAsync(CancellationToken cancellationToken)
        {
            logger.LogInformation("UmamiBackgroundSender is stopping.");

            // Signal cancellation and complete the channel
            await _cancellationTokenSource.CancelAsync();
            _channel.Writer.Complete();
            try
            {
                // Wait for the background task to complete processing any remaining items
                await Task.WhenAny(_sendTask, Task.Delay(Timeout.Infinite, cancellationToken));
            }
            catch (OperationCanceledException)
            {
                logger.LogWarning("StopAsync operation was canceled.");
            }
        }
        
                private async Task SendRequest(CancellationToken token)
    {
        logger.LogInformation("Umami background delivery started");

        while (await _channel.Reader.WaitToReadAsync(token))
        {
            while (_channel.Reader.TryRead(out var payload))
            {
                try
                {
                   using  var scope = scopeFactory.CreateScope();
                    var client = scope.ServiceProvider.GetRequiredService<UmamiClient>();
                    // Send the event via the client
                    await client.Send(payload.Payload, type:payload.EventType);

                    logger.LogInformation("Umami background event sent: {EventType}", payload.EventType);
                }
                catch (OperationCanceledException)
                {
                    logger.LogWarning("Umami background delivery canceled.");
                    return; // Exit the loop on cancellation
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "Error sending Umami background event.");
                }
            }
        }
    }

    private record SendBackgroundPayload(string EventType, UmamiPayload Payload);
    
    }

```

जैसा कि आप देख सकते हैं यह सिर्फ एक क्लासिक है `IHostedService` यह एमएसके0 हमारे सेवा संग्रह में ASP के उपयोग से जोड़ा है `services.AddHostedService<UmamiBackgroundSender>()` विधि. यह शुरू करता है `StartAsync` विधि जब अनुप्रयोग आरंभ होता है
अंदर की दृष्टि `SendRequest` विधि जहाँ जादू होता है यह है जहां हम चैनल से पढ़ते हैं और Umami सर्वर को अनुरोध भेजते हैं

यह अनुरोध भेजने के लिए वास्तविक विधियों को अपवर्जित करता है ( नीचे दिखाया गया

```csharp
public async Task TrackPageView(string url, string title, UmamiPayload? payload =null, UmamiEventData? eventData = null)

public async Task Identify(string? email = null, string? username = null,
        string? sessionId = null, string? userId = null, UmamiEventData? eventData = null)   

        public async Task IdentifySession(string sessionId, UmamiEventData? eventData = null)

public async Task Track(string eventName, UmamiEventData? eventData = null)

public async Task Send(UmamiPayload? payload = null, UmamiEventData? eventData = null,
        string eventType = "event")
```

ये सब वास्तव में करने के लिए अनुरोध पैकेज में है `SendBackgroundPayload` रिकार्ड करें और इसे चैनल को भेजें

हमारे निहित प्राप्त लूप में `SendRequest` चैनल से जब तक बंद नहीं है तब तक पठन जारी रखेगा

```csharp
  while (await _channel.Reader.WaitToReadAsync(token))
        {
            while (_channel.Reader.TryRead(out var payload))
            {
            }
        }    

```

पृष्ठभूमि सेवा में कुछ अर्थात्मकता है जो इसे सिर्फ संदेश को भेजने के लिए अनुमति देता है जैसे ही यह आ जाता है
लेकिन यह एक समस्या को उठाता है; यदि हम नहीं हैं' से एक वापसी मूल्य प्राप्त नहीं `Send` हम कैसे यह वास्तव में कुछ भी कर रहा है परीक्षण करते हैं

## परीक्षण `UmamiBackgroundSender`

तो सवाल यह है कि हम कैसे इस सेवा का परीक्षण कर रहे हैं वहाँ 5n-' के खिलाफ वास्तव में परीक्षण करने के लिए कोई प्रतिक्रिया नहीं है

जवाब है एक इंजेक्शन `HttpMessageHandler` मसखरा HttpClient हम अपने UmamiCliant में भेजते हैं. यह हमें अनुरोध को अवरोधित करने और इसे जाँचने के लिए अनुमति देगा

### इकोमोक एचटीपी संदेश हैंडलरName

आप एक नमूना HttpMessageHandler स्थापित करने के लिए पिछले लेख से याद करेंगे `EchoMockHandler` स्थिर वर्ग:

```csharp
public static class EchoMockHandler
{
    public static HttpMessageHandler Create(
        Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> responseFunc)
    {
        var mockHandler = new Mock<HttpMessageHandler>();
        mockHandler.Protected()
            .Setup<Task<HttpResponseMessage>>(
                "SendAsync",
                ItExpr.IsAny<HttpRequestMessage>(),
                ItExpr.IsAny<CancellationToken>())
            .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) =>
                responseFunc(request, cancellationToken).Result);

        return mockHandler.Object;
    }
```

आप यहाँ देख सकते हैं हम एक सेटअप करने के लिए मोक का उपयोग करते हैं `SendAsync` विधि जो HttpClient में अनुरोध ( पर आधारित एक प्रतिक्रिया लौटाएगा सभी अतुल्य अनुरोधों के माध्यम से किए जाते हैं `SendAsync`).

आप देखते हैं हम पहले मोक सेटअप

```csharp
     var mockHandler = new Mock<HttpMessageHandler>();
```

फिर हम की जादू का उपयोग करते हैं `Protected` स्थापित करने के लिए `SendAsync` विधि. यह इसलिए है कि `SendAsync` सामान्यतः सार्वजनिक API में पहुँच योग्य नहीं है `HttpMessageHandler`.

```csharp
public abstract class HttpMessageHandler : IDisposable
    {
        protected HttpMessageHandler()
        {
        }
        protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
```

तब हम सिर्फ पकड़ का उपयोग करते हैं `ItExpr.IsAny` किसी भी अनुरोध से मिलाने और जवाब वापस करने के लिए `responseFunc` हम पास करते हैं

## परीक्षण विधि

अंदर `UmamiBackgroundSender_Tests` वर्ग हम सभी परीक्षण विधियों को परिभाषित करने के लिए एक आम तरीका है

### सेटअप

```csharp
[Fact]
    public async Task Track_Page_View()
    {
        var page = "https://background.com";
        var title = "Background Example Page";
        var tcs = new TaskCompletionSource<bool>();
        // Arrange
        var handler = EchoMockHandler.Create(async (message, token) =>
        {
            try
            {
                var responseContent = EchoMockHandler.ResponseHandler(message, token);
                var jsonContent = await responseContent.Result.Content.ReadFromJsonAsync<EchoedRequest>(token);
                var content = new StringContent("{}", Encoding.UTF8, "application/json");
                Assert.Contains("api/send", message.RequestUri.ToString());
                Assert.NotNull(jsonContent);
                Assert.Equal(page, jsonContent.Payload.Url);
                Assert.Equal(title, jsonContent.Payload.Title);
                // Signal completion
                tcs.SetResult(true);

                return new HttpResponseMessage(HttpStatusCode.OK) { Content = content };
            }
            catch (Exception e)
            {
                
                tcs.SetException(e);
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }
        });

        var (backgroundSender, hostedService) = GetServices(handler);
        var cancellationToken = new CancellationToken();
        await hostedService.StartAsync(cancellationToken);
        await backgroundSender.TrackPageView(page, title);
        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1000, cancellationToken));
        if (completedTask != tcs.Task)
        {
            throw new TimeoutException("The background task did not complete in time.");
        }
        
        await tcs.Task;
        await backgroundSender.StopAsync(CancellationToken.None);
    }
```

एक बार जब हम यह परिभाषित है, हमें अपने प्रबंधन करने की जरूरत `IHostedService` परीक्षण विधि में जीवनकाल

```csharp
       var (backgroundSender, hostedService) = GetServices(handler);
        var cancellationToken = new CancellationToken();
        await hostedService.StartAsync(cancellationToken);
        await backgroundSender.TrackPageView(page, title);
        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1000, cancellationToken));
        if (completedTask != tcs.Task)
        {
            throw new TimeoutException("The background task did not complete in time.");
        }
        
        await tcs.Task;
        await backgroundSender.StopAsync(CancellationToken.None);
    }
```

आप देख सकते हैं कि हम हमारे हैंडलर में पास `GetServices` सेटअप विधि

```csharp
    private (UmamiBackgroundSender, IHostedService) GetServices(HttpMessageHandler handler)
    {
        var services = SetupExtensions.SetupServiceCollection(handler: handler);
        services.AddScoped<UmamiBackgroundSender>();
       

        services.AddScoped<IHostedService, UmamiBackgroundSender>(provider =>
            provider.GetRequiredService<UmamiBackgroundSender>());
        SetupExtensions.SetupUmamiClient(services);
        var serviceProvider = services.BuildServiceProvider();
        var backgroundSender = serviceProvider.GetRequiredService<UmamiBackgroundSender>();
        var hostedService = serviceProvider.GetRequiredService<IHostedService>();
        return (backgroundSender, hostedService);
    }
```

यहाँ हम अपने हैंडलर में हमारे सेवाओं को यह हैक करने के लिए अपने पास `UmamiClient` सेटअप

फिर हम जोड़ें `UmamiBackgroundSender` सेवा संग्रह और प्राप्त करने के लिए `IHostedService` सेवा प्रदाता से. फिर इसे प्रयोग करने के लिए यह परीक्षण वर्ग में लौटाएँ

#### होस्ट किया गया सेवा जीवनकाल

अब हम इन सभी सेटअप है कि हम बस कर सकते हैं `StartAsync` होस्ट किया गया सेवा, इसे उपयोग करें फिर यह रोके तक प्रतीक्षा करें

```csharp
        await hostedService.StartAsync(cancellationToken);
        await backgroundSender.TrackPageView(page, title);
        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1000, cancellationToken));
        if (completedTask != tcs.Task)
        {
            throw new TimeoutException("The background task did not complete in time.");
        }
        
        await tcs.Task;
        await backgroundSender.StopAsync(CancellationToken.None);
```

यह होस्ट सेवा आरंभ करेगा

### संदेश प्रबंधक

हम सबसे पहले से सेटअप करके शुरू करते हैं `EchoMockHandler` और `TaskCompletionSource` जो परीक्षण पूर्ण है संकेत करेगा. यह मुख्य परीक्षण थ्रेड में संदर्भ वापस करने के लिए महत्वपूर्ण है ताकि हम सही रूप से असफलताओं और समय समाप्तियों को पकड़ सकते हैं

 ` async (message, token) => {}` यह फ़ंक्शन है जो हम अपने नमूना हैंडलर में हम ऊपर उल्लेख किया गया है।

हमारे `EchoMockHandler.ResponseHandler` एक सहायक विधि है जो हमारी विधि में अनुरोध मुख्य भाग वापस करेगा, यह हमें सत्यापित करने के लिए अनुमति देता है संदेश के माध्यम से गुजर रहा है `UmamiClient` के लिए `HttpClient` सही रूप से

```csharp
    public static async Task<HttpResponseMessage> ResponseHandler(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        // Read the request content
        var requestBody = request.Content?.ReadAsStringAsync(cancellationToken).Result;
        // Create a response that echoes the request body
        var responseContent = requestBody ?? "No request body";
        // Return the response
        return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(responseContent, Encoding.UTF8, "application/json")
        });
    }
```

हम फिर इस प्रतिक्रिया को पकड़ें और एक में इसे Deserialize `EchoedRequest` वस्तु. यह एक सरल वस्तु है जो हम सर्वर को भेजे गए अनुरोध का प्रतिनिधित्व करता है

```csharp
public class EchoedRequest
{
    public string Type { get; set; }
    public UmamiPayload Payload { get; set; }
}
```

आप देख रहे हैं यह समाहित करता है `Type` और `Payload` के अनुरोध से . यह है क्या हम अपने परीक्षण में जाँच कर रहे हैं

```csharp
      Assert.Contains("api/send", message.RequestUri.ToString());
      Assert.NotNull(jsonContent);
      Assert.Equal(page, jsonContent.Payload.Url);
      Assert.Equal(title, jsonContent.Payload.Title);
```

क्या यह महत्वपूर्ण है कि हम असफल परीक्षणों को कैसे संभालने के लिए यहाँ हम उपयोग करने की जरूरत है मुख्य थ्रेड संदर्भ में नहीं हैं `TaskCompletionSource` मुख्य थ्रेड को वापस संकेत देने के लिए कि परीक्षण असफल है

```csharp
     catch (Exception e)
            {
                
                tcs.SetException(e);
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }
```

यह अपवाद पर सेट करेगा `TaskCompletionSource` और परीक्षण में एक 500 त्रुटि लौटाना

# अंत में

तो यह मेरी अधिक विस्तृत पोस्टों में से पहला है `IHostedService` यह सुनिश्चित करता है कि जब यहाँ की तरह यह कॉलर को कोई मान नहीं लौटाता तो यह परीक्षण करने के लिए काफी जटिल है