From 42a7ce7a884f79dd4e22f40b1f324c06605efc9f Mon Sep 17 00:00:00 2001 From: "Frank R. Haugen" Date: Sun, 14 Jan 2024 14:48:11 +0100 Subject: [PATCH] Refactored logging and testing infrastructure, added new ApiTesting namespace. 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. --- Frank.Testing.ApiTesting/ApiTestingHarness.cs | 92 +++++++++++++++++++ Frank.Testing.ApiTesting/Assertion.cs | 37 ++++++++ Frank.Testing.ApiTesting/AssertionGroup.cs | 7 ++ .../AssertionPrecursor.cs | 18 ++++ .../Frank.Testing.ApiTesting.csproj | 12 +++ Frank.Testing.ApiTesting/IAssertion.cs | 14 +++ Frank.Testing.ApiTesting/IAssertionGroup.cs | 8 ++ Frank.Testing.ApiTesting/Result.cs | 11 +++ Frank.Testing.ApiTesting/ResultGroup.cs | 7 ++ Frank.Testing.ApiTesting/ResultsFormatter.cs | 36 ++++++++ Frank.Testing.Logging/DictionaryExtensions.cs | 17 ---- .../Frank.Testing.Logging.csproj | 6 +- .../LoggingBuilderExtensions.cs | 8 +- Frank.Testing.Logging/PulseFlowTestLogger.cs | 9 +- .../ServiceCollectionExtensions.cs | 4 + Frank.Testing.Logging/SimpleTestLogger.cs | 41 +++++++++ Frank.Testing.Logging/TestLogger.cs | 37 -------- Frank.Testing.Logging/TestLoggerProvider.cs | 23 +++++ Frank.Testing.Logging/TestLoggerSettings.cs | 8 ++ .../TestLoggingOutputFlow.cs | 11 ++- .../TestOutputHelperExtensions.cs | 23 +---- .../Frank.Testing.TestOutputExtensions.csproj | 2 +- .../TestOutputExtensions.cs | 15 +++ .../TestOutputJsonExtensions.cs | 15 ++- .../ApiTesting/ApiTestingTests.cs | 83 +++++++++++++++++ .../Frank.Testing.Tests.csproj | 12 ++- .../TestLogging/TestLoggingTests.cs | 47 ++++++++++ Frank.Testing.Tests/TestLoggingTests.cs | 64 ------------- .../TestOutputCSharpExtensionsTests.cs | 6 +- .../TestOutputExtensionsTests.cs | 2 +- .../TestOutputXmlExtensionsTests.cs | 6 +- .../TestingInfrastructure/TestAddress.cs | 2 +- .../TestingInfrastructure/TestPerson.cs | 2 +- Frank.Testing.sln | 6 ++ 34 files changed, 520 insertions(+), 171 deletions(-) create mode 100644 Frank.Testing.ApiTesting/ApiTestingHarness.cs create mode 100644 Frank.Testing.ApiTesting/Assertion.cs create mode 100644 Frank.Testing.ApiTesting/AssertionGroup.cs create mode 100644 Frank.Testing.ApiTesting/AssertionPrecursor.cs create mode 100644 Frank.Testing.ApiTesting/Frank.Testing.ApiTesting.csproj create mode 100644 Frank.Testing.ApiTesting/IAssertion.cs create mode 100644 Frank.Testing.ApiTesting/IAssertionGroup.cs create mode 100644 Frank.Testing.ApiTesting/Result.cs create mode 100644 Frank.Testing.ApiTesting/ResultGroup.cs create mode 100644 Frank.Testing.ApiTesting/ResultsFormatter.cs delete mode 100644 Frank.Testing.Logging/DictionaryExtensions.cs create mode 100644 Frank.Testing.Logging/SimpleTestLogger.cs delete mode 100644 Frank.Testing.Logging/TestLogger.cs create mode 100644 Frank.Testing.Logging/TestLoggerProvider.cs create mode 100644 Frank.Testing.Logging/TestLoggerSettings.cs create mode 100644 Frank.Testing.TestOutputExtensions/TestOutputExtensions.cs create mode 100644 Frank.Testing.Tests/ApiTesting/ApiTestingTests.cs create mode 100644 Frank.Testing.Tests/TestLogging/TestLoggingTests.cs delete mode 100644 Frank.Testing.Tests/TestLoggingTests.cs rename Frank.Testing.Tests/{ => TestOutputExtensionsTests}/TestOutputCSharpExtensionsTests.cs (72%) rename Frank.Testing.Tests/{ => TestOutputExtensionsTests}/TestOutputExtensionsTests.cs (92%) rename Frank.Testing.Tests/{ => TestOutputExtensionsTests}/TestOutputXmlExtensionsTests.cs (87%) diff --git a/Frank.Testing.ApiTesting/ApiTestingHarness.cs b/Frank.Testing.ApiTesting/ApiTestingHarness.cs new file mode 100644 index 0000000..c6644b9 --- /dev/null +++ b/Frank.Testing.ApiTesting/ApiTestingHarness.cs @@ -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 AssertionGroups { get; set; } + + public async Task> RunAssertionsAsync() + { + var assertionResults = new List(); + + foreach (var group in AssertionGroups) + { + var tasks = new List>(); + + foreach (var assertion in group.Assertions) + { + tasks.Add(RunAssertionAsync(assertion)); + } + + var results = await Task.WhenAll(tasks); + + assertionResults.AddRange(results); + } + + return assertionResults; + } + + private async Task 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; + } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/Assertion.cs b/Frank.Testing.ApiTesting/Assertion.cs new file mode 100644 index 0000000..ea1175f --- /dev/null +++ b/Frank.Testing.ApiTesting/Assertion.cs @@ -0,0 +1,37 @@ +using System.Net; + +namespace Frank.Testing.ApiTesting; + +public class Assertion +{ + 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; } + + public HttpMethod Method { get; set; } + + public TimeSpan Timeout { get; set; } + + public HttpContent? RequestContent { get; set; } + + public HttpContent? ExpectedResponseContent { get; set; } + + public HttpStatusCode ExpectedResponseCode { get; set; } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/AssertionGroup.cs b/Frank.Testing.ApiTesting/AssertionGroup.cs new file mode 100644 index 0000000..181c37d --- /dev/null +++ b/Frank.Testing.ApiTesting/AssertionGroup.cs @@ -0,0 +1,7 @@ +namespace Frank.Testing.ApiTesting; + +public class AssertionGroup +{ + public string GroupName { get; set; } + public List Assertions { get; set; } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/AssertionPrecursor.cs b/Frank.Testing.ApiTesting/AssertionPrecursor.cs new file mode 100644 index 0000000..9a28faa --- /dev/null +++ b/Frank.Testing.ApiTesting/AssertionPrecursor.cs @@ -0,0 +1,18 @@ +using System.Net; + +namespace Frank.Testing.ApiTesting; + +public class AssertionPrecursor +{ + public string? Name { get; set; } + + public Uri Endpoint { get; set; } + + public HttpMethod Method { get; set; } + + public TimeSpan Timeout { get; set; } + + public TResponse? ExpectedResponse { get; set; } + + public HttpStatusCode ExpectedResponseCode { get; set; } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/Frank.Testing.ApiTesting.csproj b/Frank.Testing.ApiTesting/Frank.Testing.ApiTesting.csproj new file mode 100644 index 0000000..6c3c1ee --- /dev/null +++ b/Frank.Testing.ApiTesting/Frank.Testing.ApiTesting.csproj @@ -0,0 +1,12 @@ + + + + Test your api with this simple library + api, test, rest, http, client + + + + + + + diff --git a/Frank.Testing.ApiTesting/IAssertion.cs b/Frank.Testing.ApiTesting/IAssertion.cs new file mode 100644 index 0000000..588747b --- /dev/null +++ b/Frank.Testing.ApiTesting/IAssertion.cs @@ -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; } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/IAssertionGroup.cs b/Frank.Testing.ApiTesting/IAssertionGroup.cs new file mode 100644 index 0000000..eccb990 --- /dev/null +++ b/Frank.Testing.ApiTesting/IAssertionGroup.cs @@ -0,0 +1,8 @@ +namespace Frank.Testing.ApiTesting; + +public interface IAssertionGroup +{ + string GroupName { get; set; } + + SortedList Assertions { get; set; } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/Result.cs b/Frank.Testing.ApiTesting/Result.cs new file mode 100644 index 0000000..764cd4a --- /dev/null +++ b/Frank.Testing.ApiTesting/Result.cs @@ -0,0 +1,11 @@ +namespace Frank.Testing.ApiTesting; + +public class Result +{ + public string AssertionName { get; set; } + public bool IsSuccess { get; set; } + public string? ErrorMessage { get; set; } + public TimeSpan ElapsedTime { get; set; } + + public HttpContent? ResponseContent { get; set; } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/ResultGroup.cs b/Frank.Testing.ApiTesting/ResultGroup.cs new file mode 100644 index 0000000..16e99b7 --- /dev/null +++ b/Frank.Testing.ApiTesting/ResultGroup.cs @@ -0,0 +1,7 @@ +namespace Frank.Testing.ApiTesting; + +public class ResultGroup +{ + public string GroupName { get; set; } + public List AssertionResults { get; set; } +} \ No newline at end of file diff --git a/Frank.Testing.ApiTesting/ResultsFormatter.cs b/Frank.Testing.ApiTesting/ResultsFormatter.cs new file mode 100644 index 0000000..e6e886e --- /dev/null +++ b/Frank.Testing.ApiTesting/ResultsFormatter.cs @@ -0,0 +1,36 @@ +namespace Frank.Testing.ApiTesting; + +using System.Collections.Generic; +using System.Xml.Linq; + +public class ResultsFormatter +{ + public string FormatResults(List 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(); + } +} diff --git a/Frank.Testing.Logging/DictionaryExtensions.cs b/Frank.Testing.Logging/DictionaryExtensions.cs deleted file mode 100644 index 5618bb5..0000000 --- a/Frank.Testing.Logging/DictionaryExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Frank.Testing.Logging; - -internal static class DictionaryExtensions -{ - public static TValue GetOrCreate( - this IDictionary dictionary, - TKey key, - Func valueFactory) - { - if (dictionary.TryGetValue(key, out TValue? value) && value != null) - return value; - - value = valueFactory(); - dictionary[key] = value; - return value; - } -} \ No newline at end of file diff --git a/Frank.Testing.Logging/Frank.Testing.Logging.csproj b/Frank.Testing.Logging/Frank.Testing.Logging.csproj index bcb2738..17ca64a 100644 --- a/Frank.Testing.Logging/Frank.Testing.Logging.csproj +++ b/Frank.Testing.Logging/Frank.Testing.Logging.csproj @@ -6,11 +6,11 @@ - - + + - + diff --git a/Frank.Testing.Logging/LoggingBuilderExtensions.cs b/Frank.Testing.Logging/LoggingBuilderExtensions.cs index 71862b8..cec129d 100644 --- a/Frank.Testing.Logging/LoggingBuilderExtensions.cs +++ b/Frank.Testing.Logging/LoggingBuilderExtensions.cs @@ -19,9 +19,15 @@ public static class LoggingBuilderExtensions /// The modified ILoggingBuilder with the test logging added. public static ILoggingBuilder AddPulseFlowTestLoggingProvider(this ILoggingBuilder builder, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) { + builder.ClearProviders(); builder.AddPulseFlow(); builder.Services.AddSingleton(outputHelper); - builder.Services.AddSingleton(); + builder.Services.Configure(options => + { + options.LogLevel = logLevel; + }); + builder.Services.AddPulseFlow(flowBuilder => flowBuilder.AddFlow()); + builder.Services.AddSingleton(provider => new TestLoggerProvider(provider.GetRequiredService())); return builder; } } \ No newline at end of file diff --git a/Frank.Testing.Logging/PulseFlowTestLogger.cs b/Frank.Testing.Logging/PulseFlowTestLogger.cs index d4f0116..54c62de 100644 --- a/Frank.Testing.Logging/PulseFlowTestLogger.cs +++ b/Frank.Testing.Logging/PulseFlowTestLogger.cs @@ -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(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - => conduit.SendAsync(new LogPulse(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 state) where TState : notnull => new PulseFlowLoggerScope(state); -} \ No newline at end of file +} + +public class PulseFlowTestLogger(IConduit conduit) : PulseFlowTestLogger(conduit, typeof(T).GetDisplayName()), ILogger; \ No newline at end of file diff --git a/Frank.Testing.Logging/ServiceCollectionExtensions.cs b/Frank.Testing.Logging/ServiceCollectionExtensions.cs index 6d39ba4..0eb4a2a 100644 --- a/Frank.Testing.Logging/ServiceCollectionExtensions.cs +++ b/Frank.Testing.Logging/ServiceCollectionExtensions.cs @@ -16,6 +16,10 @@ public static class ServiceCollectionExtensions /// The modified IServiceCollection with the test logging added. public static IServiceCollection AddTestLogging(this IServiceCollection services, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) { + services.Configure(options => + { + options.LogLevel = logLevel; + }); services.AddLogging(builder => builder.AddPulseFlowTestLoggingProvider(outputHelper, logLevel)); return services; } diff --git a/Frank.Testing.Logging/SimpleTestLogger.cs b/Frank.Testing.Logging/SimpleTestLogger.cs new file mode 100644 index 0000000..99011fe --- /dev/null +++ b/Frank.Testing.Logging/SimpleTestLogger.cs @@ -0,0 +1,41 @@ +using Frank.Reflection; + +using Microsoft.Extensions.Logging; + +using Xunit.Abstractions; + +namespace Frank.Testing.Logging; + +public class SimpleTestLogger : SimpleTestLogger, ILogger +{ + 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 state) where TState : notnull + => new TestLoggerScope(state); + + public bool IsEnabled(LogLevel logLevel) => logLevel >= _logLevel; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (logLevel < _logLevel) + return; + + _outputHelper.WriteLine($"[{logLevel}]: {formatter(state, exception)}"); + } +} \ No newline at end of file diff --git a/Frank.Testing.Logging/TestLogger.cs b/Frank.Testing.Logging/TestLogger.cs deleted file mode 100644 index 395ab20..0000000 --- a/Frank.Testing.Logging/TestLogger.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.Extensions.Logging; - -using Xunit.Abstractions; - -namespace Frank.Testing.Logging; - -public class TestLogger : ILogger -{ - public ITestOutputHelper OutputHelper { get; } - public LogLevel LogLevel { get; } - public string? CategoryName { get; } - - public TestLogger(ITestOutputHelper outputHelper, LogLevel logLevel, string? categoryName = null) - { - OutputHelper = outputHelper; - LogLevel = logLevel; - CategoryName = categoryName; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - if (logLevel < LogLevel) - return; - - OutputHelper.WriteLine($"{logLevel}: {formatter(state, exception)}"); - } - - public bool IsEnabled(LogLevel logLevel) - { - return logLevel >= LogLevel; - } - - public IDisposable? BeginScope(TState state) where TState : notnull - { - return new TestLoggerScope(state); - } -} \ No newline at end of file diff --git a/Frank.Testing.Logging/TestLoggerProvider.cs b/Frank.Testing.Logging/TestLoggerProvider.cs new file mode 100644 index 0000000..013a8e1 --- /dev/null +++ b/Frank.Testing.Logging/TestLoggerProvider.cs @@ -0,0 +1,23 @@ +using System.Collections.Concurrent; + +using Frank.PulseFlow; + +using Microsoft.Extensions.Logging; + +namespace Frank.Testing.Logging; + +public class TestLoggerProvider(IConduit conduit) : ILoggerProvider +{ + private readonly ConcurrentDictionary _loggers = new(); + + public ILogger CreateLogger(string categoryName) + { + if (_loggers.TryGetValue(categoryName, out var logger)) + return logger; + + var newLogger = new PulseFlowTestLogger(conduit, categoryName); + return _loggers.GetOrAdd(categoryName, newLogger); + } + + public void Dispose() => _loggers.Clear(); +} \ No newline at end of file diff --git a/Frank.Testing.Logging/TestLoggerSettings.cs b/Frank.Testing.Logging/TestLoggerSettings.cs new file mode 100644 index 0000000..307aa14 --- /dev/null +++ b/Frank.Testing.Logging/TestLoggerSettings.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Logging; + +namespace Frank.Testing.Logging; + +public class TestLoggerSettings +{ + public LogLevel LogLevel { get; set; } = LogLevel.Information; +} \ No newline at end of file diff --git a/Frank.Testing.Logging/TestLoggingOutputFlow.cs b/Frank.Testing.Logging/TestLoggingOutputFlow.cs index 86128ad..cd3a1a9 100644 --- a/Frank.Testing.Logging/TestLoggingOutputFlow.cs +++ b/Frank.Testing.Logging/TestLoggingOutputFlow.cs @@ -10,11 +10,14 @@ namespace Frank.Testing.Logging; /// public class TestLoggingOutputFlow(ITestOutputHelper outputHelper) : IFlow { - public Task HandleAsync(IPulse pulse, CancellationToken cancellationToken) + public async Task HandleAsync(IPulse pulse, CancellationToken cancellationToken) { - outputHelper.WriteLine(pulse.ToString()); - return Task.CompletedTask; + if (pulse is LogPulse logPulse) + { + outputHelper.WriteLine(logPulse.Message); + await Task.CompletedTask; + } } - public bool CanHandle(Type pulseType) => pulseType == typeof(LogPulse<>); + public bool CanHandle(Type pulseType) => pulseType == typeof(LogPulse); } \ No newline at end of file diff --git a/Frank.Testing.Logging/TestOutputHelperExtensions.cs b/Frank.Testing.Logging/TestOutputHelperExtensions.cs index 1bb7e86..f21ed9e 100644 --- a/Frank.Testing.Logging/TestOutputHelperExtensions.cs +++ b/Frank.Testing.Logging/TestOutputHelperExtensions.cs @@ -1,7 +1,4 @@ -using Frank.PulseFlow.Logging; -using Frank.Reflection; - -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Xunit.Abstractions; @@ -14,22 +11,8 @@ public static class TestOutputHelperExtensions /// /// The type of the class to which the created logger will be associated. /// The ITestOutputHelper instance used for logging. - /// The name of the log category. (optional) /// /// An instance of that can be used for logging tests. public static ILogger CreateTestLogger(this ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) - => new TestLogger(outputHelper, logLevel, typeof(T).GetDisplayName()); - - /// - /// Creates a test logger factory using the specified . - /// - /// The test output helper. - /// - /// A new instance of for testing purposes. - public static ILoggerFactory CreateTestLoggerFactory(this ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) - { - var factory = new LoggerFactory(); - // factory.AddProvider(new PulseFlowLoggerProvider()); - return factory; - } -} \ No newline at end of file + => new SimpleTestLogger(outputHelper, logLevel); +} diff --git a/Frank.Testing.TestOutputExtensions/Frank.Testing.TestOutputExtensions.csproj b/Frank.Testing.TestOutputExtensions/Frank.Testing.TestOutputExtensions.csproj index 8a14d3c..a438db1 100644 --- a/Frank.Testing.TestOutputExtensions/Frank.Testing.TestOutputExtensions.csproj +++ b/Frank.Testing.TestOutputExtensions/Frank.Testing.TestOutputExtensions.csproj @@ -8,7 +8,7 @@ - + diff --git a/Frank.Testing.TestOutputExtensions/TestOutputExtensions.cs b/Frank.Testing.TestOutputExtensions/TestOutputExtensions.cs new file mode 100644 index 0000000..04e96cc --- /dev/null +++ b/Frank.Testing.TestOutputExtensions/TestOutputExtensions.cs @@ -0,0 +1,15 @@ +using System.Text.Json; + +namespace Xunit.Abstractions; + +public static class TestOutputExtensions +{ + /// + /// Writes the specified object's string representation followed by the current line terminator to the output. + /// + /// The type of the object to write. + /// The ITestOutputHelper instance. + /// The object to write. + /// Thrown when unable to serialize the object. + public static void WriteLine(this ITestOutputHelper outputHelper, T? source) => outputHelper.WriteLine(JsonSerializer.Serialize(source, outputHelper.GetDefaultJsonSerializerOptions())); +} \ No newline at end of file diff --git a/Frank.Testing.TestOutputExtensions/TestOutputJsonExtensions.cs b/Frank.Testing.TestOutputExtensions/TestOutputJsonExtensions.cs index 0f331cc..8551a26 100644 --- a/Frank.Testing.TestOutputExtensions/TestOutputJsonExtensions.cs +++ b/Frank.Testing.TestOutputExtensions/TestOutputJsonExtensions.cs @@ -5,15 +5,6 @@ namespace Xunit.Abstractions; public static class TestOutputJsonExtensions { - /// - /// Writes the specified object's string representation followed by the current line terminator to the output. - /// - /// The type of the object to write. - /// The ITestOutputHelper instance. - /// The object to write. - /// Thrown when unable to serialize the object. - public static void WriteLine(this ITestOutputHelper outputHelper, T? source) => outputHelper.WriteLine(JsonSerializer.Serialize(source, JsonSerializerOptions)); - /// /// Writes a JSON representation of the specified object to the output helper. /// @@ -26,5 +17,11 @@ public static class TestOutputJsonExtensions /// public static void WriteJson(this ITestOutputHelper outputHelper, T? source, JsonSerializerOptions? options = null) => outputHelper.WriteLine(options == null ? JsonSerializer.Serialize(source, JsonSerializerOptions) : JsonSerializer.Serialize(source, options)); + /// + /// Gets the default JsonSerializerOptions. + /// + /// The default JsonSerializerOptions. + public static JsonSerializerOptions GetDefaultJsonSerializerOptions(this ITestOutputHelper _) => JsonSerializerOptions; + private static JsonSerializerOptions JsonSerializerOptions => new() { Converters = { new JsonStringEnumConverter() }, WriteIndented = true, ReferenceHandler = ReferenceHandler.IgnoreCycles, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; } \ No newline at end of file diff --git a/Frank.Testing.Tests/ApiTesting/ApiTestingTests.cs b/Frank.Testing.Tests/ApiTesting/ApiTestingTests.cs new file mode 100644 index 0000000..5d45a5e --- /dev/null +++ b/Frank.Testing.Tests/ApiTesting/ApiTestingTests.cs @@ -0,0 +1,83 @@ +using System.Net; + +using Frank.Testing.ApiTesting; + +using NSubstitute; + +using Xunit.Abstractions; + +namespace Frank.Testing.Tests.ApiTesting; + +public class ApiTestingTests +{ + private readonly ITestOutputHelper _outputHelper; + + public ApiTestingTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + [Fact] + public async Task Test1() + { + var harness = new ApiTestingHarness(new MockHttpClientFactory()); + + var assertionGroup1 = new AssertionGroup + { + GroupName = "Group 1", + Assertions = new List + { + new Assertion + { + Name = "Is BRREG API alive?", + Endpoint = new Uri("https://data.brreg.no"), + Method = HttpMethod.Get, + Timeout = TimeSpan.FromSeconds(10), + ExpectedResponseCode = HttpStatusCode.OK + }, + new Assertion + { + Name = "Can I download something from the BRREG API?", + Endpoint = new Uri("https://data.brreg.no/enhetsregisteret/api/enheter"), + Method = HttpMethod.Get, + Timeout = TimeSpan.FromSeconds(10), + ExpectedResponseCode = HttpStatusCode.OK + } + } + }; + + var assertionGroup2 = new AssertionGroup + { + GroupName = "Group 2", + Assertions = new List + { + // Add more assertions... + } + }; + + harness.AssertionGroups = new List { assertionGroup1, assertionGroup2 }; + + var results = await harness.RunAssertionsAsync(); + + foreach (var result in results) + { + _outputHelper.WriteLine($"Assertion: {result.AssertionName}"); + _outputHelper.WriteLine($"Success: {result.IsSuccess}"); + _outputHelper.WriteLine($"Elapsed time: {result.ElapsedTime}"); + if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) + { + _outputHelper.WriteLine($"Error Message: {result.ErrorMessage}"); + } + _outputHelper.WriteLine(""); + } + + _outputHelper.WriteLine("This is a test"); + + Assert.True(results.All(arg => arg.IsSuccess)); + } + + private class MockHttpClientFactory : IHttpClientFactory + { + public HttpClient CreateClient(string name) => new(); + } +} \ No newline at end of file diff --git a/Frank.Testing.Tests/Frank.Testing.Tests.csproj b/Frank.Testing.Tests/Frank.Testing.Tests.csproj index 136322f..d779898 100644 --- a/Frank.Testing.Tests/Frank.Testing.Tests.csproj +++ b/Frank.Testing.Tests/Frank.Testing.Tests.csproj @@ -1,10 +1,6 @@ - net8.0 - enable - enable - false true @@ -13,7 +9,12 @@ - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -25,6 +26,7 @@ + diff --git a/Frank.Testing.Tests/TestLogging/TestLoggingTests.cs b/Frank.Testing.Tests/TestLogging/TestLoggingTests.cs new file mode 100644 index 0000000..c17d3f2 --- /dev/null +++ b/Frank.Testing.Tests/TestLogging/TestLoggingTests.cs @@ -0,0 +1,47 @@ +using Frank.Testing.Logging; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using Xunit.Abstractions; + +namespace Frank.Testing.Tests.TestLogging; + +public class TestLoggingTests(ITestOutputHelper outputHelper) +{ + [Fact] + public async Task Test1() + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + var host = CreateHostBuilder().Build(); + + await host.RunAsync(cancellationTokenSource.Token); + } + + private IHostBuilder CreateHostBuilder() + { + return Host.CreateDefaultBuilder() + .ConfigureLogging(logging => + { + logging.AddPulseFlowTestLoggingProvider(outputHelper, LogLevel.Information); + }) + .ConfigureServices((context, services) => + { + services.AddHostedService(); + }); + } + + private class MyService(ILogger logger) : BackgroundService + { + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using var scope = logger.BeginScope("Titans are {Description}", "awesome"); + while (!stoppingToken.IsCancellationRequested) + { + logger.LogInformation("Hello from MyService"); + await Task.Delay(1000, stoppingToken); + } + } + } +} \ No newline at end of file diff --git a/Frank.Testing.Tests/TestLoggingTests.cs b/Frank.Testing.Tests/TestLoggingTests.cs deleted file mode 100644 index 57a0df4..0000000 --- a/Frank.Testing.Tests/TestLoggingTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Frank.PulseFlow; -using Frank.PulseFlow.Logging; -using Frank.Testing.Logging; - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -using Xunit.Abstractions; - -namespace Frank.Testing.Tests; - -public class TestLoggingTests -{ - private readonly ITestOutputHelper _outputHelper; - - public TestLoggingTests(ITestOutputHelper outputHelper) - { - _outputHelper = outputHelper; - } - - [Fact] - public void Test1() - { - var host = CreateHostBuilder().Build(); - - host.Start(); - } - - - - private IHostBuilder CreateHostBuilder() - { - return Host.CreateDefaultBuilder() - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.AddPulseFlow(); - }) - .ConfigureServices((context, services) => - { - services.AddSingleton(_outputHelper); - services.AddPulseFlow(builder => - { - builder.AddFlow(); - }); - - services.AddHostedService(); - }); - } - - private class MyService : BackgroundService - { - private readonly ILogger _logger; - - public MyService(ILogger logger) => _logger = logger; - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - _logger.LogInformation("Hello from MyService"); - await Task.Delay(1000, stoppingToken); - } - } -} \ No newline at end of file diff --git a/Frank.Testing.Tests/TestOutputCSharpExtensionsTests.cs b/Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputCSharpExtensionsTests.cs similarity index 72% rename from Frank.Testing.Tests/TestOutputCSharpExtensionsTests.cs rename to Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputCSharpExtensionsTests.cs index 938dc82..7c22582 100644 --- a/Frank.Testing.Tests/TestOutputCSharpExtensionsTests.cs +++ b/Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputCSharpExtensionsTests.cs @@ -1,8 +1,10 @@ -using JetBrains.Annotations; +using Frank.Testing.Tests.TestingInfrastructure; + +using JetBrains.Annotations; using Xunit.Abstractions; -namespace Frank.Testing.Tests; +namespace Frank.Testing.Tests.TestOutputExtensionsTests; [TestSubject(typeof(TestOutputCSharpExtensions))] public class TestOutputCSharpExtensionsTests(ITestOutputHelper outputHelper) diff --git a/Frank.Testing.Tests/TestOutputExtensionsTests.cs b/Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputExtensionsTests.cs similarity index 92% rename from Frank.Testing.Tests/TestOutputExtensionsTests.cs rename to Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputExtensionsTests.cs index a861aa8..e1cb571 100644 --- a/Frank.Testing.Tests/TestOutputExtensionsTests.cs +++ b/Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputExtensionsTests.cs @@ -2,7 +2,7 @@ using Xunit.Abstractions; -namespace Frank.Testing.Tests; +namespace Frank.Testing.Tests.TestOutputExtensionsTests; [TestSubject(typeof(TestOutputJsonExtensions))] public class TestOutputExtensionsTests diff --git a/Frank.Testing.Tests/TestOutputXmlExtensionsTests.cs b/Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputXmlExtensionsTests.cs similarity index 87% rename from Frank.Testing.Tests/TestOutputXmlExtensionsTests.cs rename to Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputXmlExtensionsTests.cs index 8e3d3df..edd184b 100644 --- a/Frank.Testing.Tests/TestOutputXmlExtensionsTests.cs +++ b/Frank.Testing.Tests/TestOutputExtensionsTests/TestOutputXmlExtensionsTests.cs @@ -1,8 +1,10 @@ -using JetBrains.Annotations; +using Frank.Testing.Tests.TestingInfrastructure; + +using JetBrains.Annotations; using Xunit.Abstractions; -namespace Frank.Testing.Tests; +namespace Frank.Testing.Tests.TestOutputExtensionsTests; [TestSubject(typeof(TestOutputXmlExtensions))] public class TestOutputXmlExtensionsTests(ITestOutputHelper outputHelper) diff --git a/Frank.Testing.Tests/TestingInfrastructure/TestAddress.cs b/Frank.Testing.Tests/TestingInfrastructure/TestAddress.cs index 3208c74..fef89b5 100644 --- a/Frank.Testing.Tests/TestingInfrastructure/TestAddress.cs +++ b/Frank.Testing.Tests/TestingInfrastructure/TestAddress.cs @@ -1,4 +1,4 @@ -namespace Frank.Testing.Tests; +namespace Frank.Testing.Tests.TestingInfrastructure; public class TestAddress { diff --git a/Frank.Testing.Tests/TestingInfrastructure/TestPerson.cs b/Frank.Testing.Tests/TestingInfrastructure/TestPerson.cs index b0ad391..d16f6f5 100644 --- a/Frank.Testing.Tests/TestingInfrastructure/TestPerson.cs +++ b/Frank.Testing.Tests/TestingInfrastructure/TestPerson.cs @@ -1,4 +1,4 @@ -namespace Frank.Testing.Tests; +namespace Frank.Testing.Tests.TestingInfrastructure; public class TestPerson { diff --git a/Frank.Testing.sln b/Frank.Testing.sln index def1f84..0e5aa9c 100644 --- a/Frank.Testing.sln +++ b/Frank.Testing.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.Tests", "Fran EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.TestOutputExtensions", "Frank.Testing.TestOutputExtensions\Frank.Testing.TestOutputExtensions.csproj", "{1C2E4123-AA8D-4A4E-B2EC-712A739F628B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.ApiTesting", "Frank.Testing.ApiTesting\Frank.Testing.ApiTesting.csproj", "{3B30D75C-5B95-4E87-B862-7E8DC49038DF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,5 +43,9 @@ Global {1C2E4123-AA8D-4A4E-B2EC-712A739F628B}.Debug|Any CPU.Build.0 = Debug|Any CPU {1C2E4123-AA8D-4A4E-B2EC-712A739F628B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1C2E4123-AA8D-4A4E-B2EC-712A739F628B}.Release|Any CPU.Build.0 = Release|Any CPU + {3B30D75C-5B95-4E87-B862-7E8DC49038DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B30D75C-5B95-4E87-B862-7E8DC49038DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B30D75C-5B95-4E87-B862-7E8DC49038DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B30D75C-5B95-4E87-B862-7E8DC49038DF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal