From 8c61cca47ee849b6ceb3b080c9abf1d34a97e340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20D=C3=B6rnen?= Date: Tue, 16 Dec 2025 16:18:14 +0100 Subject: [PATCH 1/6] Make configuration work independent of whether appsettings json files are provided --- .../ConfigurationTests.cs | 5 +++-- .../Fixtures/TestProjectFixture.cs | 6 +++--- .../Fixtures/TestProjectFixtureWithoutAppsettings.cs | 10 ++++++++++ src/Abstracts/TestBedFixture.cs | 11 ++++------- 4 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixtureWithoutAppsettings.cs diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs index 4a6de58..8f4ec9c 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs @@ -1,11 +1,12 @@ namespace Xunit.Microsoft.DependencyInjection.ExampleTests; -public class ConfigurationTests : TestBed +public class ConfigurationTests : TestBed { private const string Key = "CONFIG_KEY"; private const string Value = "Value"; - public ConfigurationTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : base(testOutputHelper, fixture) + public ConfigurationTests(ITestOutputHelper testOutputHelper, TestProjectFixtureWithoutAppsettings fixture) + : base(testOutputHelper, fixture) { Environment.SetEnvironmentVariable(Key, Value); } diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixture.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixture.cs index bf91c0b..c740ee8 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixture.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixture.cs @@ -2,7 +2,7 @@ public class TestProjectFixture : TestBedFixture { - protected override void AddServices(IServiceCollection services, IConfiguration? configuration) + protected override void AddServices(IServiceCollection services, IConfiguration configuration) => services // Transient services - new instance for each injection .AddTransient() @@ -23,8 +23,8 @@ protected override void AddServices(IServiceCollection services, IConfiguration? .AddTransient>(provider => () => provider.GetService()!) // Configure options - .Configure(config => configuration?.GetSection("Options").Bind(config)) - .Configure(config => configuration?.GetSection(nameof(SecretValues)).Bind(config)); + .Configure(config => configuration.GetSection("Options").Bind(config)) + .Configure(config => configuration.GetSection(nameof(SecretValues)).Bind(config)); protected override ValueTask DisposeAsyncCore() => new(); diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixtureWithoutAppsettings.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixtureWithoutAppsettings.cs new file mode 100644 index 0000000..5241fdf --- /dev/null +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixtureWithoutAppsettings.cs @@ -0,0 +1,10 @@ +namespace Xunit.Microsoft.DependencyInjection.ExampleTests.Fixtures; + +public class TestProjectFixtureWithoutAppsettings : TestBedFixture +{ + protected override void AddServices(IServiceCollection services, IConfiguration configuration) + { + } + + protected override ValueTask DisposeAsyncCore() => new(); +} \ No newline at end of file diff --git a/src/Abstracts/TestBedFixture.cs b/src/Abstracts/TestBedFixture.cs index 22f64d9..bb61580 100644 --- a/src/Abstracts/TestBedFixture.cs +++ b/src/Abstracts/TestBedFixture.cs @@ -123,12 +123,12 @@ public async ValueTask DisposeAsync() /// /// Adds services to the service collection. Called once before building the provider. /// - protected abstract void AddServices(IServiceCollection services, IConfiguration? configuration); + protected abstract void AddServices(IServiceCollection services, IConfiguration configuration); /// /// Returns the test application settings descriptors (JSON files) to include. /// - protected abstract IEnumerable GetTestAppSettings(); + protected virtual IEnumerable GetTestAppSettings() => []; /// /// Override to asynchronously clean up resources created by the fixture. @@ -147,13 +147,10 @@ protected virtual ILoggingBuilder AddLoggingProvider(ILoggingBuilder loggingBuil /// protected virtual void AddUserSecrets(IConfigurationBuilder configurationBuilder) { } - private IConfigurationRoot? GetConfigurationRoot() + private IConfigurationRoot GetConfigurationRoot() { var testAppSettings = GetTestAppSettings(); - return - testAppSettings.All(setting => !string.IsNullOrEmpty(setting.Filename)) - ? GetConfigurationRoot(testAppSettings) - : default; + return GetConfigurationRoot(testAppSettings); } private IConfigurationRoot GetConfigurationRoot(IEnumerable configurationFiles) From b4dc253ec0ff9eb159b39b465a6850e4f769377f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20D=C3=B6rnen?= Date: Tue, 16 Dec 2025 18:33:04 +0100 Subject: [PATCH 2/6] Programatically set (random) user secrets using dotnet command. Then assert the secrets are available in the application via configuration. --- .../SecretValues.cs | 4 +- .../UserSecretTests.cs | 44 ++++++++++++++----- .../appsettings.json | 4 -- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs index a98e991..97ff487 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs @@ -2,6 +2,6 @@ public record SecretValues { - public string? Secret1 { get; set; } - public string? Secret2 { get; set; } + public required string Secret1 { get; set; } + public required string Secret2 { get; set; } } diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs index bf5bf58..ca82ee6 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs @@ -1,23 +1,45 @@  +using System.Diagnostics; using Microsoft.Extensions.Options; namespace Xunit.Microsoft.DependencyInjection.ExampleTests; -public class UserSecretTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : TestBed(testOutputHelper, fixture) +public class UserSecretTests : TestBed { + private static readonly Guid Guid = Guid.NewGuid(); // Ensure unique user secrets per test run + + private readonly string _secret1Value = $"secret1-{Guid}"; + private readonly string _secret2Value = $"secret2-{Guid}"; + + public UserSecretTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : base(testOutputHelper, fixture) + { + SetSecret(nameof(SecretValues.Secret1), _secret1Value); + SetSecret(nameof(SecretValues.Secret2), _secret2Value); + } + [Fact] public void TestSecretValues() { - /* - * TODO: Create a user secret entry like the following payload in user secrets and remove the same from appsettings.json file: - * - * "SecretValues": { - * "Secret1": "secret1value", - * "Secret2": "secret2value" - * } - */ var secretValues = _fixture.GetService>(_testOutputHelper)!.Value; - Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty); - Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty); + Assert.Equal(secretValues.Secret1, _secret1Value); + Assert.Equal(secretValues.Secret2, _secret2Value); + } + + private void SetSecret(string secretName, string secretValue) + { + var startInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"user-secrets set {nameof(SecretValues)}:{secretName} {secretValue}", + WorkingDirectory = Path.Combine(Environment.CurrentDirectory, "..", "..", "..") + }; + var proc = Process.Start(startInfo); + ArgumentNullException.ThrowIfNull(proc); + + proc.WaitForExit(); + if (proc.ExitCode != 0) + { + throw new Exception("Failed to set user secret"); + } } } \ No newline at end of file diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json index 68f26b6..16a9d81 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json @@ -1,9 +1,5 @@ { "Options": { "Rate": 10 - }, - "SecretValues": { - "Secret1": "StoreSecret1InUserSecrets", - "Secret2": "StoreSecret2InUserSecrets" } } From 5c94822c49255727ccd895cfbf773d38d495f98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20D=C3=B6rnen?= Date: Thu, 18 Dec 2025 17:37:33 +0100 Subject: [PATCH 3/6] Revert "Programatically set (random) user secrets using dotnet command. Then assert the secrets are available in the application via configuration." This reverts commit b4dc253ec0ff9eb159b39b465a6850e4f769377f. --- .../SecretValues.cs | 4 +- .../UserSecretTests.cs | 44 +++++-------------- .../appsettings.json | 4 ++ 3 files changed, 17 insertions(+), 35 deletions(-) diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs index 97ff487..a98e991 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/SecretValues.cs @@ -2,6 +2,6 @@ public record SecretValues { - public required string Secret1 { get; set; } - public required string Secret2 { get; set; } + public string? Secret1 { get; set; } + public string? Secret2 { get; set; } } diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs index ca82ee6..bf5bf58 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/UserSecretTests.cs @@ -1,45 +1,23 @@  -using System.Diagnostics; using Microsoft.Extensions.Options; namespace Xunit.Microsoft.DependencyInjection.ExampleTests; -public class UserSecretTests : TestBed +public class UserSecretTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : TestBed(testOutputHelper, fixture) { - private static readonly Guid Guid = Guid.NewGuid(); // Ensure unique user secrets per test run - - private readonly string _secret1Value = $"secret1-{Guid}"; - private readonly string _secret2Value = $"secret2-{Guid}"; - - public UserSecretTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : base(testOutputHelper, fixture) - { - SetSecret(nameof(SecretValues.Secret1), _secret1Value); - SetSecret(nameof(SecretValues.Secret2), _secret2Value); - } - [Fact] public void TestSecretValues() { + /* + * TODO: Create a user secret entry like the following payload in user secrets and remove the same from appsettings.json file: + * + * "SecretValues": { + * "Secret1": "secret1value", + * "Secret2": "secret2value" + * } + */ var secretValues = _fixture.GetService>(_testOutputHelper)!.Value; - Assert.Equal(secretValues.Secret1, _secret1Value); - Assert.Equal(secretValues.Secret2, _secret2Value); - } - - private void SetSecret(string secretName, string secretValue) - { - var startInfo = new ProcessStartInfo - { - FileName = "dotnet", - Arguments = $"user-secrets set {nameof(SecretValues)}:{secretName} {secretValue}", - WorkingDirectory = Path.Combine(Environment.CurrentDirectory, "..", "..", "..") - }; - var proc = Process.Start(startInfo); - ArgumentNullException.ThrowIfNull(proc); - - proc.WaitForExit(); - if (proc.ExitCode != 0) - { - throw new Exception("Failed to set user secret"); - } + Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty); + Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty); } } \ No newline at end of file diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json index 16a9d81..68f26b6 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/appsettings.json @@ -1,5 +1,9 @@ { "Options": { "Rate": 10 + }, + "SecretValues": { + "Secret1": "StoreSecret1InUserSecrets", + "Secret2": "StoreSecret2InUserSecrets" } } From 5fcbc4675e656c6751f4459f8ecf2ef09d020557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20D=C3=B6rnen?= Date: Thu, 18 Dec 2025 17:43:24 +0100 Subject: [PATCH 4/6] Add explicit test class for testing configuration without test appsettings --- .../ConfigurationTests.cs | 4 ++-- .../ConfigurationTestsWithoutAppsettings.cs | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs index 8f4ec9c..01b392b 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTests.cs @@ -1,11 +1,11 @@ namespace Xunit.Microsoft.DependencyInjection.ExampleTests; -public class ConfigurationTests : TestBed +public class ConfigurationTests : TestBed { private const string Key = "CONFIG_KEY"; private const string Value = "Value"; - public ConfigurationTests(ITestOutputHelper testOutputHelper, TestProjectFixtureWithoutAppsettings fixture) + public ConfigurationTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : base(testOutputHelper, fixture) { Environment.SetEnvironmentVariable(Key, Value); diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs new file mode 100644 index 0000000..009c33e --- /dev/null +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs @@ -0,0 +1,23 @@ +namespace Xunit.Microsoft.DependencyInjection.ExampleTests; + +public class ConfigurationTestsWithoutAppsettings : TestBed +{ + private const string Key = "CONFIG_KEY"; + private const string Value = "Value"; + + public ConfigurationTestsWithoutAppsettings(ITestOutputHelper testOutputHelper, TestProjectFixtureWithoutAppsettings fixture) + : base(testOutputHelper, fixture) + { + Environment.SetEnvironmentVariable(Key, Value); + } + + [Fact] + public void EnvironmentVariablesViaConstructorAreAvailable() + { + _fixture.GetServiceProvider(_testOutputHelper); + + var value = _fixture.Configuration!.GetValue(Key); + + Assert.Equal(Value, value); + } +} From 16600bb2aba27d604be08be50615bd34e0fba6a1 Mon Sep 17 00:00:00 2001 From: Arash Sabet Date: Sun, 21 Dec 2025 09:51:04 -0500 Subject: [PATCH 5/6] Update pipeline configs and clean up test code Bump .NET SDK version to 10.0.101 and increment patch/revision numbers in both Azure pipeline YAML files. Remove unnecessary blank lines in ConfigurationTestsWithoutAppsettings.cs for cleaner test code. --- azure-pipeline-PR.yml | 4 ++-- azure-pipelines.yml | 4 ++-- .../ConfigurationTestsWithoutAppsettings.cs | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/azure-pipeline-PR.yml b/azure-pipeline-PR.yml index 833fb53..6e800d5 100644 --- a/azure-pipeline-PR.yml +++ b/azure-pipeline-PR.yml @@ -1,7 +1,7 @@ variables: Major: 10 Minor: 0 - Patch: 1 + Patch: 2 BuildConfiguration: Release name: $(Major).$(Minor).$(Patch).$(rev:r) @@ -17,7 +17,7 @@ steps: displayName: 'Use .NET 10.0 sdk' inputs: packageType: sdk - version: 10.0.100 + version: 10.0.101 installationPath: $(Agent.ToolsDirectory)/dotnet - script: echo Started restoring the source code - task: DotNetCoreCLI@2 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2f2b462..355a521 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,7 +1,7 @@ variables: Major: 10 Minor: 0 - Revision: 1 + Revision: 2 BuildConfiguration: Release name: $(Major).$(Minor).$(Revision) @@ -25,7 +25,7 @@ steps: displayName: 'Use .NET 10.0 sdk' inputs: packageType: sdk - version: 10.0.100 + version: 10.0.101 installationPath: $(Agent.ToolsDirectory)/dotnet - script: echo Started restoring the source code - task: DotNetCoreCLI@2 diff --git a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs index 009c33e..43199b8 100644 --- a/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs +++ b/examples/Xunit.Microsoft.DependencyInjection.ExampleTests/ConfigurationTestsWithoutAppsettings.cs @@ -15,9 +15,7 @@ public ConfigurationTestsWithoutAppsettings(ITestOutputHelper testOutputHelper, public void EnvironmentVariablesViaConstructorAreAvailable() { _fixture.GetServiceProvider(_testOutputHelper); - var value = _fixture.Configuration!.GetValue(Key); - Assert.Equal(Value, value); } } From 319612d94494fd6a981648b96d9f82514a9cd2cb Mon Sep 17 00:00:00 2001 From: Arash Sabet Date: Sun, 21 Dec 2025 09:56:57 -0500 Subject: [PATCH 6/6] Skip empty filenames in configuration loading Update GetConfigurationRoot to ignore TestAppSettings entries with null or empty filenames when adding JSON files to the configuration builder. --- src/Abstracts/TestBedFixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abstracts/TestBedFixture.cs b/src/Abstracts/TestBedFixture.cs index bb61580..145764f 100644 --- a/src/Abstracts/TestBedFixture.cs +++ b/src/Abstracts/TestBedFixture.cs @@ -155,7 +155,7 @@ private IConfigurationRoot GetConfigurationRoot() private IConfigurationRoot GetConfigurationRoot(IEnumerable configurationFiles) { - foreach (var configurationFile in configurationFiles) + foreach (var configurationFile in configurationFiles.Where(appSetting => !string.IsNullOrEmpty(appSetting.Filename))) { ConfigurationBuilder.AddJsonFile(configurationFile.Filename!, optional: configurationFile.IsOptional); }