This commit is contained in:
Stef Heyenrath
2026-03-06 07:37:51 +01:00
parent 7bbfe7a887
commit f83b4bd264
13 changed files with 104 additions and 132 deletions

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -33,24 +33,9 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.2.0.2\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Hosting, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="SimMetrics.Net, Version=1.0.5.0, Culture=neutral, PublicKeyToken=c58dc06d59f3391b, processorArchitecture=MSIL"> <Reference Include="SimMetrics.Net, Version=1.0.5.0, Culture=neutral, PublicKeyToken=c58dc06d59f3391b, processorArchitecture=MSIL">
<HintPath>..\..\packages\SimMetrics.Net.1.0.5\lib\net45\SimMetrics.Net.dll</HintPath> <HintPath>..\..\packages\SimMetrics.Net.1.0.5\lib\net45\SimMetrics.Net.dll</HintPath>
</Reference> </Reference>
@@ -59,29 +44,17 @@
<Reference Include="System.Configuration.Install" /> <Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Management" /> <Reference Include="System.Management" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Numerics" /> <Reference Include="System.Numerics" />
<Reference Include="System.Security" /> <Reference Include="System.Security" />
<Reference Include="System.ServiceProcess" /> <Reference Include="System.ServiceProcess" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="XPath2, Version=1.1.4.0, Culture=neutral, PublicKeyToken=463c6d7fb740c7e5, processorArchitecture=MSIL">
<HintPath>..\..\packages\XPath2.1.1.4\lib\net452\XPath2.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Installer.cs"> <Compile Include="Installer.cs">
@@ -115,10 +88,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="log4net"> <PackageReference Include="log4net">
<Version>3.2.0</Version> <Version>3.3.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core">
<Version>2.3.9</Version>
</PackageReference> </PackageReference>
<PackageReference Include="WireMock.Net"> <PackageReference Include="WireMock.Net">
<Version>1.12.0</Version> <Version>1.25.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -2,6 +2,7 @@
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Security.Authentication;
using WireMock.HttpsCertificate; using WireMock.HttpsCertificate;
using WireMock.Settings; using WireMock.Settings;
@@ -12,10 +13,13 @@ internal static class HttpClientBuilder
public static HttpClient Build(HttpClientSettings settings) public static HttpClient Build(HttpClientSettings settings)
{ {
#if NET8_0_OR_GREATER #if NET8_0_OR_GREATER
var handler = new HttpClientHandler var handler = new HttpClientHandler
{ {
CheckCertificateRevocationList = false, CheckCertificateRevocationList = false,
SslProtocols = System.Security.Authentication.SslProtocols.Tls13 | System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls, #pragma warning disable SYSLIB0039 // Type or member is obsolete
SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
#pragma warning restore SYSLIB0039 // Type or member is obsolete
ServerCertificateCustomValidationCallback = (_, _, _, _) => true, ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
}; };

View File

@@ -48,7 +48,7 @@ internal static class CertificateLoader
if (!string.IsNullOrEmpty(options.X509CertificateFilePath)) if (!string.IsNullOrEmpty(options.X509CertificateFilePath))
{ {
if (options.X509CertificateFilePath.EndsWith(ExtensionPem, StringComparison.OrdinalIgnoreCase)) if (options.X509CertificateFilePath!.EndsWith(ExtensionPem, StringComparison.OrdinalIgnoreCase))
{ {
// PEM logic based on: https://www.scottbrady91.com/c-sharp/pem-loading-in-dotnet-core-and-dotnet // PEM logic based on: https://www.scottbrady91.com/c-sharp/pem-loading-in-dotnet-core-and-dotnet
#if NET8_0_OR_GREATER #if NET8_0_OR_GREATER

View File

@@ -17,7 +17,7 @@ namespace WireMock.Owin;
internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
{ {
public IWireMockLogger Logger { get; set; } public IWireMockLogger Logger { get; set; } = new WireMockConsoleLogger();
public TimeSpan? RequestProcessingDelay { get; set; } public TimeSpan? RequestProcessingDelay { get; set; }

View File

@@ -586,7 +586,7 @@ public partial class WireMockServer
{ {
if (TryParseGuidFromRequestMessage(requestMessage, out var guid)) if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{ {
var entry = LogEntries.SingleOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); var entry = LogEntries.SingleOrDefault(r => r.RequestMessage != null && !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
if (entry is { }) if (entry is { })
{ {
var model = new LogEntryMapper(_options).Map(entry); var model = new LogEntryMapper(_options).Map(entry);
@@ -615,7 +615,7 @@ public partial class WireMockServer
{ {
var logEntryMapper = new LogEntryMapper(_options); var logEntryMapper = new LogEntryMapper(_options);
var result = LogEntries var result = LogEntries
.Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) .Where(r => r.RequestMessage != null && !r.RequestMessage.Path.StartsWith("/__admin/"))
.Select(logEntryMapper.Map); .Select(logEntryMapper.Map);
return ToJson(result); return ToJson(result);
@@ -637,10 +637,10 @@ public partial class WireMockServer
var request = (Request)InitRequestBuilder(requestModel); var request = (Request)InitRequestBuilder(requestModel);
var dict = new Dictionary<ILogEntry, RequestMatchResult>(); var dict = new Dictionary<ILogEntry, RequestMatchResult>();
foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) foreach (var logEntry in LogEntries.Where(le => le.RequestMessage != null && !le.RequestMessage.Path.StartsWith("/__admin/")))
{ {
var requestMatchResult = new RequestMatchResult(); var requestMatchResult = new RequestMatchResult();
if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) if (request.GetMatchingScore(logEntry.RequestMessage!, requestMatchResult) > MatchScores.AlmostPerfect)
{ {
dict.Add(logEntry, requestMatchResult); dict.Add(logEntry, requestMatchResult);
} }
@@ -659,7 +659,7 @@ public partial class WireMockServer
Guid.TryParse(value.ToString(), out var mappingGuid) Guid.TryParse(value.ToString(), out var mappingGuid)
) )
{ {
var logEntries = LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/") && le.MappingGuid == mappingGuid); var logEntries = LogEntries.Where(le => le.RequestMessage != null && !le.RequestMessage.Path.StartsWith("/__admin/") && le.MappingGuid == mappingGuid);
var logEntryMapper = new LogEntryMapper(_options); var logEntryMapper = new LogEntryMapper(_options);
var result = logEntries.Select(logEntryMapper.Map); var result = logEntries.Select(logEntryMapper.Map);
return ToJson(result); return ToJson(result);

View File

@@ -4,66 +4,65 @@ using Scriban;
using Scriban.Parsing; using Scriban.Parsing;
using Scriban.Runtime; using Scriban.Runtime;
namespace WireMock.Transformers.Scriban namespace WireMock.Transformers.Scriban;
internal class WireMockListAccessor : IListAccessor, IObjectAccessor
{ {
internal class WireMockListAccessor : IListAccessor, IObjectAccessor #region IListAccessor
public int GetLength(TemplateContext context, SourceSpan span, object target)
{ {
#region IListAccessor throw new NotImplementedException();
public int GetLength(TemplateContext context, SourceSpan span, object target)
{
throw new NotImplementedException();
}
public object GetValue(TemplateContext context, SourceSpan span, object target, int index)
{
return target.ToString();
}
public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object value)
{
throw new NotImplementedException();
}
#endregion
#region IObjectAccessor
public int GetMemberCount(TemplateContext context, SourceSpan span, object target)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetMembers(TemplateContext context, SourceSpan span, object target)
{
throw new NotImplementedException();
}
public bool HasMember(TemplateContext context, SourceSpan span, object target, string member)
{
throw new NotImplementedException();
}
public bool TryGetValue(TemplateContext context, SourceSpan span, object target, string member, out object value)
{
throw new NotImplementedException();
}
public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value)
{
throw new NotImplementedException();
}
public bool TryGetItem(TemplateContext context, SourceSpan span, object target, object index, out object value)
{
throw new NotImplementedException();
}
public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object value)
{
throw new NotImplementedException();
}
public bool HasIndexer => throw new NotImplementedException();
public Type IndexType => throw new NotImplementedException();
#endregion
} }
public object GetValue(TemplateContext context, SourceSpan span, object target, int index)
{
return target?.ToString() ?? string.Empty;
}
public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object value)
{
throw new NotImplementedException();
}
#endregion
#region IObjectAccessor
public int GetMemberCount(TemplateContext context, SourceSpan span, object target)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetMembers(TemplateContext context, SourceSpan span, object target)
{
throw new NotImplementedException();
}
public bool HasMember(TemplateContext context, SourceSpan span, object target, string member)
{
throw new NotImplementedException();
}
public bool TryGetValue(TemplateContext context, SourceSpan span, object target, string member, out object value)
{
throw new NotImplementedException();
}
public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value)
{
throw new NotImplementedException();
}
public bool TryGetItem(TemplateContext context, SourceSpan span, object target, object index, out object value)
{
throw new NotImplementedException();
}
public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object value)
{
throw new NotImplementedException();
}
public bool HasIndexer => throw new NotImplementedException();
public Type IndexType => throw new NotImplementedException();
#endregion
} }

View File

@@ -17,7 +17,7 @@ public class RequestBuilderUsingMethodTests
// Assert // Assert
var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers"); var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers");
matchers.Count.Should().Be(1); matchers.Count.Should().Be(1);
((matchers[0] as RequestMessageMethodMatcher).Methods).Should().ContainSingle("CONNECT"); (matchers[0] as RequestMessageMethodMatcher).Methods.Should().ContainSingle("CONNECT");
} }
[Fact] [Fact]
@@ -29,7 +29,7 @@ public class RequestBuilderUsingMethodTests
// Assert // Assert
var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers"); var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers");
matchers.Count.Should().Be(1); matchers.Count.Should().Be(1);
((matchers[0] as RequestMessageMethodMatcher).Methods).Should().ContainSingle("OPTIONS"); (matchers[0] as RequestMessageMethodMatcher).Methods.Should().ContainSingle("OPTIONS");
} }
[Fact] [Fact]
@@ -41,7 +41,7 @@ public class RequestBuilderUsingMethodTests
// Assert // Assert
var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers"); var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers");
matchers.Count.Should().Be(1); matchers.Count.Should().Be(1);
((matchers[0] as RequestMessageMethodMatcher).Methods).Should().ContainSingle("PATCH"); (matchers[0] as RequestMessageMethodMatcher).Methods.Should().ContainSingle("PATCH");
} }
[Fact] [Fact]
@@ -53,7 +53,7 @@ public class RequestBuilderUsingMethodTests
// Assert // Assert
var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers"); var matchers = requestBuilder.GetPrivateFieldValue<IList<IRequestMatcher>>("_requestMatchers");
matchers.Count.Should().Be(1); matchers.Count.Should().Be(1);
((matchers[0] as RequestMessageMethodMatcher).Methods).Should().ContainSingle("TRACE"); (matchers[0] as RequestMessageMethodMatcher).Methods.Should().ContainSingle("TRACE");
} }
[Fact] [Fact]

View File

@@ -109,8 +109,8 @@ public class ResponseWithHandlebarsJsonPathTests
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings); var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings);
// Assert // Assert
JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson!); var j = JObject.FromObject(response.Message.BodyData!.BodyAsJson!);
j["x"].Value<long>().Should().Be(99); j["x"]?.Value<long>().Should().Be(99);
} }
[Fact] [Fact]
@@ -163,7 +163,7 @@ public class ResponseWithHandlebarsJsonPathTests
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings); var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings);
// Assert // Assert
response.Message.BodyData.BodyAsString.Should().Be($"{{{Environment.NewLine} \"Name\": \"Acme Co\",{Environment.NewLine} \"Products\": [{Environment.NewLine} {{{Environment.NewLine} \"Name\": \"Anvil\",{Environment.NewLine} \"Price\": 50{Environment.NewLine} }}{Environment.NewLine} ]{Environment.NewLine}}}"); response.Message.BodyData!.BodyAsString.Should().Be($"{{{Environment.NewLine} \"Name\": \"Acme Co\",{Environment.NewLine} \"Products\": [{Environment.NewLine} {{{Environment.NewLine} \"Name\": \"Anvil\",{Environment.NewLine} \"Price\": 50{Environment.NewLine} }}{Environment.NewLine} ]{Environment.NewLine}}}");
} }
[Fact] [Fact]

View File

@@ -46,9 +46,9 @@ public class ResponseWithHandlebarsLinqTests
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings); var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings);
// Assert // Assert
JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson); var j = JObject.FromObject(response.Message.BodyData!.BodyAsJson!);
j["x"].Should().NotBeNull(); j["x"].Should().NotBeNull();
j["x"].ToString().Should().Be("/pathtest"); j["x"]?.ToString().Should().Be("/pathtest");
} }
[Fact(Skip = "DynamicLinq")] [Fact(Skip = "DynamicLinq")]
@@ -76,9 +76,8 @@ public class ResponseWithHandlebarsLinqTests
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings); var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings);
// Assert // Assert
JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson); var j = JObject.FromObject(response.Message.BodyData!.BodyAsJson!);
j["x"].Should().NotBeNull(); j["x"].Should().NotBeNull().And.Subject.ToString().Should().Be("Test_123");
j["x"].ToString().Should().Be("Test_123");
} }
[Fact(Skip = "DynamicLinq")] [Fact(Skip = "DynamicLinq")]

View File

@@ -3,7 +3,6 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Moq; using Moq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using WireMock.Handlers; using WireMock.Handlers;
using WireMock.Models; using WireMock.Models;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
@@ -69,9 +68,8 @@ public class ResponseWithHandlebarsXegerTests
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings); var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of<HttpContext>(), request, _settings);
// Assert // Assert
JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson); var j = JObject.FromObject(response.Message.BodyData!.BodyAsJson!);
j["Number"].Value<int>().Should().BeGreaterThan(1000).And.BeLessThan(9999); j["Number"]?.Value<int>().Should().BeGreaterThan(1000).And.BeLessThan(9999);
j["Postcode"].Value<string>().Should().NotBeEmpty(); j["Postcode"]?.Value<string>().Should().NotBeEmpty();
} }
} }

View File

@@ -1,20 +1,16 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
namespace WireMock.Net.Tests.Serialization namespace WireMock.Net.Tests.Serialization;
public class CustomPathParamMatcherModel
{ {
public class CustomPathParamMatcherModel public string Path { get; set; }
public Dictionary<string, string> PathParams { get; set; }
public CustomPathParamMatcherModel(string path, Dictionary<string, string> pathParams)
{ {
public string Path { get; set; } Path = path;
public Dictionary<string, string> PathParams { get; set; } PathParams = pathParams;
public CustomPathParamMatcherModel()
{
}
public CustomPathParamMatcherModel(string path, Dictionary<string, string> pathParams)
{
Path = path;
PathParams = pathParams;
}
} }
} }

View File

@@ -10,7 +10,7 @@ public static class TestUtils
{ {
var field = obj.GetType().GetTypeInfo().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); var field = obj.GetType().GetTypeInfo().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
return (T)field.GetValue(obj); return (T)field!.GetValue(obj)!;
} }
/// <summary> /// <summary>
@@ -27,8 +27,8 @@ public static class TestUtils
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
Type t = obj.GetType(); Type? t = obj.GetType();
FieldInfo fi = null; FieldInfo? fi = null;
while (fi == null && t != null) while (fi == null && t != null)
{ {
fi = t.GetField(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); fi = t.GetField(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);