-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
…amespace. This commit includes significant changes to the testing and logging infrastructure. The improvements resulted in cleaner API for test logging, allowing multiple kinds of test loggers such as PulseFlow and SimpleTestLogger. Moreover, a new namespace, ApiTesting, was established which includes Assertion, AssertionGroup and Result classes. This will enhance the organization and readability of test cases. This commit also included relevant changes to the project and solution files.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System.Diagnostics; | ||
|
||
namespace Frank.Testing.ApiTesting; | ||
|
||
public class ApiTestingHarness | ||
{ | ||
private readonly IHttpClientFactory _httpClientFactory; | ||
|
||
public ApiTestingHarness(IHttpClientFactory httpClientFactory) | ||
{ | ||
_httpClientFactory = httpClientFactory; | ||
} | ||
|
||
public List<AssertionGroup> AssertionGroups { get; set; } | ||
|
||
public async Task<List<Result>> RunAssertionsAsync() | ||
{ | ||
var assertionResults = new List<Result>(); | ||
|
||
foreach (var group in AssertionGroups) | ||
{ | ||
var tasks = new List<Task<Result>>(); | ||
|
||
foreach (var assertion in group.Assertions) | ||
{ | ||
tasks.Add(RunAssertionAsync(assertion)); | ||
} | ||
|
||
var results = await Task.WhenAll(tasks); | ||
|
||
assertionResults.AddRange(results); | ||
} | ||
|
||
return assertionResults; | ||
} | ||
|
||
private async Task<Result> RunAssertionAsync(IAssertion assertion) | ||
{ | ||
var result = new Result | ||
{ | ||
AssertionName = assertion.Name, | ||
IsSuccess = false, | ||
ErrorMessage = string.Empty | ||
}; | ||
|
||
var stopwatch = Stopwatch.StartNew(); | ||
try | ||
{ | ||
using var client = _httpClientFactory.CreateClient(); | ||
client.Timeout = assertion.Timeout; | ||
|
||
var request = new HttpRequestMessage(assertion.Method, assertion.Endpoint); | ||
request.Content = assertion.RequestContent; | ||
|
||
var response = await client.SendAsync(request); | ||
|
||
if (response.StatusCode == assertion.ExpectedResponseCode) | ||
{ | ||
if (assertion.ExpectedResponseContent != null) | ||
{ | ||
var responseContent = await response.Content.ReadAsStringAsync(); | ||
|
||
if (responseContent == await assertion.ExpectedResponseContent.ReadAsStringAsync()) | ||
{ | ||
result.IsSuccess = true; | ||
} | ||
else | ||
{ | ||
result.ErrorMessage = "Response content does not match expected content."; | ||
} | ||
} | ||
else | ||
{ | ||
result.IsSuccess = true; | ||
} | ||
} | ||
else | ||
{ | ||
result.ErrorMessage = $"Expected response code {assertion.ExpectedResponseCode}, but received {response.StatusCode}."; | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
result.ErrorMessage = ex.Message; | ||
} | ||
stopwatch.Stop(); | ||
|
||
result.ElapsedTime = stopwatch.Elapsed; | ||
|
||
return result; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System.Net; | ||
|
||
namespace Frank.Testing.ApiTesting; | ||
|
||
public class Assertion<TRequest, TResponse> | ||
{ | ||
public string? Name { get; set; } | ||
|
||
public Uri Endpoint { get; set; } | ||
|
||
public HttpMethod Method { get; set; } | ||
|
||
public TimeSpan Timeout { get; set; } | ||
|
||
public TRequest? Request { get; set; } | ||
|
||
public TResponse? ExpectedResponse { get; set; } | ||
|
||
public HttpStatusCode ExpectedResponseCode { get; set; } | ||
} | ||
|
||
public class Assertion : IAssertion | ||
{ | ||
public string? Name { get; set; } | ||
|
||
public Uri Endpoint { get; set; } | ||
Check warning on line 26 in Frank.Testing.ApiTesting/Assertion.cs GitHub Actions / Release Job / Release Job
|
||
|
||
public HttpMethod Method { get; set; } | ||
Check warning on line 28 in Frank.Testing.ApiTesting/Assertion.cs GitHub Actions / Release Job / Release Job
|
||
|
||
public TimeSpan Timeout { get; set; } | ||
|
||
public HttpContent? RequestContent { get; set; } | ||
|
||
public HttpContent? ExpectedResponseContent { get; set; } | ||
|
||
public HttpStatusCode ExpectedResponseCode { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Frank.Testing.ApiTesting; | ||
|
||
public class AssertionGroup | ||
{ | ||
public string GroupName { get; set; } | ||
public List<IAssertion> Assertions { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System.Net; | ||
|
||
namespace Frank.Testing.ApiTesting; | ||
|
||
public class AssertionPrecursor<TResponse> | ||
{ | ||
public string? Name { get; set; } | ||
|
||
public Uri Endpoint { get; set; } | ||
Check warning on line 9 in Frank.Testing.ApiTesting/AssertionPrecursor.cs GitHub Actions / Release Job / Release Job
|
||
|
||
public HttpMethod Method { get; set; } | ||
Check warning on line 11 in Frank.Testing.ApiTesting/AssertionPrecursor.cs GitHub Actions / Release Job / Release Job
|
||
|
||
public TimeSpan Timeout { get; set; } | ||
|
||
public TResponse? ExpectedResponse { get; set; } | ||
|
||
public HttpStatusCode ExpectedResponseCode { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Description>Test your api with this simple library</Description> | ||
<PackageTags>api, test, rest, http, client</PackageTags> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System.Net; | ||
|
||
namespace Frank.Testing.ApiTesting; | ||
|
||
public interface IAssertion | ||
{ | ||
string? Name { get; set; } | ||
Uri Endpoint { get; set; } | ||
HttpMethod Method { get; set; } | ||
TimeSpan Timeout { get; set; } | ||
HttpContent? RequestContent { get; set; } | ||
HttpContent? ExpectedResponseContent { get; set; } | ||
HttpStatusCode ExpectedResponseCode { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Frank.Testing.ApiTesting; | ||
|
||
public interface IAssertionGroup | ||
{ | ||
string GroupName { get; set; } | ||
|
||
SortedList<uint, IAssertion> Assertions { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace Frank.Testing.ApiTesting; | ||
|
||
public class Result | ||
{ | ||
public string AssertionName { get; set; } | ||
Check warning on line 5 in Frank.Testing.ApiTesting/Result.cs GitHub Actions / Release Job / Release Job
|
||
public bool IsSuccess { get; set; } | ||
public string? ErrorMessage { get; set; } | ||
public TimeSpan ElapsedTime { get; set; } | ||
|
||
public HttpContent? ResponseContent { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Frank.Testing.ApiTesting; | ||
|
||
public class ResultGroup | ||
{ | ||
public string GroupName { get; set; } | ||
Check warning on line 5 in Frank.Testing.ApiTesting/ResultGroup.cs GitHub Actions / Release Job / Release Job
|
||
public List<Result> AssertionResults { get; set; } | ||
Check warning on line 6 in Frank.Testing.ApiTesting/ResultGroup.cs GitHub Actions / Release Job / Release Job
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
namespace Frank.Testing.ApiTesting; | ||
|
||
using System.Collections.Generic; | ||
using System.Xml.Linq; | ||
|
||
public class ResultsFormatter | ||
{ | ||
public string FormatResults(List<ResultGroup> groups) | ||
{ | ||
var xDocument = new XDocument(); | ||
var root = new XElement("TestGroups"); | ||
|
||
foreach (var group in groups) | ||
{ | ||
var groupElement = new XElement("Group", | ||
new XAttribute("Name", group.GroupName)); | ||
|
||
foreach (var assertion in group.AssertionResults) | ||
{ | ||
var testElement = new XElement("Test", | ||
new XElement("Name", assertion.AssertionName), | ||
new XElement("Success", assertion.IsSuccess), | ||
new XElement("ErrorMessage", assertion.ErrorMessage ?? string.Empty), | ||
new XElement("ElapsedTime", assertion.ElapsedTime.ToString("g")) | ||
); | ||
|
||
groupElement.Add(testElement); | ||
} | ||
|
||
root.Add(groupElement); | ||
} | ||
|
||
xDocument.Add(root); | ||
return xDocument.ToString(); | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,19 @@ | ||
using Frank.PulseFlow; | ||
using Frank.PulseFlow.Logging; | ||
using Frank.Reflection; | ||
|
||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
public class PulseFlowTestLogger(IConduit conduit) : ILogger | ||
public class PulseFlowTestLogger(IConduit conduit, string categoryName) : ILogger | ||
{ | ||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) | ||
=> conduit.SendAsync(new LogPulse<TState>(logLevel, eventId, state, exception, formatter, "TestLogger")).GetAwaiter().GetResult(); | ||
=> conduit.SendAsync(new LogPulse(logLevel, eventId, exception, categoryName, formatter(state, exception))).GetAwaiter().GetResult(); | ||
|
||
public bool IsEnabled(LogLevel logLevel) => true; | ||
|
||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => new PulseFlowLoggerScope<TState>(state); | ||
} | ||
} | ||
|
||
public class PulseFlowTestLogger<T>(IConduit conduit) : PulseFlowTestLogger(conduit, typeof(T).GetDisplayName()), ILogger<T>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using Frank.Reflection; | ||
|
||
using Microsoft.Extensions.Logging; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
public class SimpleTestLogger<T> : SimpleTestLogger, ILogger<T> | ||
{ | ||
public SimpleTestLogger(ITestOutputHelper outputHelper, LogLevel logLevel) : base(outputHelper, logLevel, typeof(T).GetDisplayName()) | ||
{ | ||
} | ||
} | ||
|
||
public class SimpleTestLogger : ILogger | ||
{ | ||
private readonly ITestOutputHelper _outputHelper; | ||
private readonly LogLevel _logLevel; | ||
private string _categoryName; | ||
|
||
public SimpleTestLogger(ITestOutputHelper outputHelper, LogLevel logLevel, string categoryName) | ||
{ | ||
_outputHelper = outputHelper; | ||
_logLevel = logLevel; | ||
_categoryName = categoryName; | ||
} | ||
|
||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull | ||
=> new TestLoggerScope<TState>(state); | ||
|
||
public bool IsEnabled(LogLevel logLevel) => logLevel >= _logLevel; | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) | ||
{ | ||
if (logLevel < _logLevel) | ||
return; | ||
|
||
_outputHelper.WriteLine($"[{logLevel}]: {formatter(state, exception)}"); | ||
} | ||
} |