# यूनिट परीक्षण Umami.Net - मोक का उपयोग किए बिना Umami डाटा के परीक्षण

# परिचय

इस श्रृंखला के पिछले भाग में जहां मैंने परीक्षण किया[ Umami.नेट ट्रैकिंग विधि ](/blog/unittestingumaminet)

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

## समस्या

पिछले भाग में मैं मुझे एक दिन देने के लिए मोक का उपयोग किया `Mock<HttpMessageHandler>` और वापस हैडलर में उपयोग किया गया `UmamiClient`, यह एक आम पैटर्न है जब परीक्षण कोड का प्रयोग करता है `HttpClient`. इस पोस्ट में मैं आपको दिखाएँगा कि कैसे नए परीक्षण `UmamiDataService` बिना 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;
    }
```

## मोक का प्रयोग क्यों करें

मोक एक शक्तिशाली मोकिंग लाइब्रेरी है जो आपको इंटरफेस और वर्गों के लिए मोक ऑब्जेक्ट बनाने की अनुमति देता है।

उदाहरण मैं ऊपर दिया है कि परीक्षण में बहुत लचीलाता देता है `UmamiClient` वर्ग `,` लेकिन यह भी कुछ त्रुटियां है`. यह ' UGLY कोड और बहुत सारे काम करता है मैं नहीं हूँ `UmamiDataService` मैंने एक अलग दृष्टिकोण का प्रयास करने के लिए निर्णय लिया

# UmamiDataService का परीक्षण कर रहा है

 `UmamiDataService` Umami के लिए एक भावी जोड़ है। . नेट लाइब्रेरी जो आपको Umami से डेटा प्राप्त करने की अनुमति देगा जैसे कि एक पृष्ठ में कितनी दृश्यों को देखना था। [उमामी API केवल जावास्क्रिप्ट के माध्यम से काम करता है](https://umami.is/docs/api/website-stats)तो उस डेटा के साथ खेलना चाहते हुए मैं एक C के लिए एक क्लाइंट बनाने का प्रयास किया

 `UmamiDataService` वर्ग बहुविध आंशिक वर्गों में विभाजित किया जाता है (में विधियाँ सुपर लंबा हैं `PageViews` विधि

आप देख सकते हैं कि कोड का MUCH PageViewsRequest वर्ग में पास से क्वेरीस्ट्रिंग बना रहा है (इसके करने के लिए अन्य तरीके हैं लेकिन यह

<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>
जैसा कि आप देख सकते हैं यह वास्तव में सिर्फ एक क्वेरी स्ट्रिंग बनाता है [पिछला लेख](/blog/unittestinglogginginaspnetcore) इस पर कुछ विवरण के लिए ) और फिर Umami API को कॉल करता है

## उमामी डेटा सेवा का परीक्षण

UmamiClient, परीक्षण के विपरीत मैं परीक्षण करने का निर्णय किया `UmamiDataService` बिना Moq का उपयोग करने के बजाय मैं एक सरल बनाया `DelegatingHandler` वर्ग जो मुझे अनुरोध पर प्रश्न पूछने के लिए अनुमति देता है फिर एक प्रतिक्रिया लौटाएँ. यह मोक का उपयोग करने से अधिक सरल दृष्टिकोण है और मुझे परीक्षण करने की अनुमति देती है `UmamiDataService` बिना मसखरापन करने के `HttpClient`.

नीचे कोड में आप देख सकते हैं मैं सिर्फ विस्तार `DelegatingHandler` और ओवरराइड `SendAsync` विधि. यह विधि मुझे अनुरोध की जांच करने और अनुरोध पर आधारित एक प्रतिक्रिया लौटाने के लिए अनुमति देता है

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

## सेटअप

नया सेटअप करने के लिए `UmamiDataService` इस ह्यान्डलर का उपयोग करने के लिए समान रूप से सरल है

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

आपll see I just set up the `ServiceCollection`, जोड़ें `FakeLogger<T>` ( फिर से देखें [इस पर विस्तृत जानकारी के लिए अंतिम लेख](/blog/unittestinglogginginaspnetcore) और फिर सेटअप करने के लिए `UmamiData` उपयोक्ता नाम और पासवर्ड के साथ सेवा मैं उपयोग करना चाहता हूँ (ताकि मैं असफलता का परीक्षण कर सकता है

तब मैं बुलाता हूँ `services.SetupUmamiData(username, password);` जो एक विस्तार विधि मैं सेटअप करने के लिए बनाया है `UmamiDataService` के साथ `UmamiDataDelegatingHandler` और `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>();
    }
```

आप देख सकते हैं कि यह है जहां मैं हैक में जुड़ा है `UmamiDataDelegatingHandler` और `AuthService` के लिए `UmamiDataService`. इस तरीके से यह संरचना है कि `AuthService` 'owns `HttpClient` और `UmamiDataService` उपयोग करता है `AuthService` Umami API के साथ कॉल करने के लिए `bearer` टोकन और `BaseAddress` पहले से सेट है

## टेस्ट्स

वास्तव में यह वास्तव में इस वास्तव में परीक्षण को सरल बनाता है। `DelegatingHandler` और मैं अनुरोध के आधार पर एक प्रतिक्रिया सिमुलित करता हूँ.

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

### प्रतिक्रिया सिमुलेशन

इस विधि के लिए प्रतिक्रिया नकल करने के लिए आप '\ll याद करेंगे मैं इस रेखा में है `UmamiDataDelegatingHandler`:

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

यह सिर्फ क्वेरी स्ट्रिंग से सूचना खींचता है और एक 'realisticM SK1 प्रतिक्रिया बनाता है Live Tests पर आधारित मैं'I have compiledMSC4again very little docs on this\).\Youll see I test for the number of days between the start and end date and then return a response with the same numbers of days\.\

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

# अंत में

तो कि यह वास्तव में यह है-' यह एक बहुत सरल परीक्षण करने के लिए ' `HttpClient` बिना मोक का उपयोग करने के अनुरोध और मैं यह सोचता हूँ