Skip to content

Commit 9e5a0ad

Browse files
authored
Merge pull request #263 from Umplify/262-support-user-secrets-in-the-test-fixture
262 support user secrets in the test fixture
2 parents fd650ad + a7d4d07 commit 9e5a0ad

File tree

11 files changed

+130
-42
lines changed

11 files changed

+130
-42
lines changed

.github/workflows/auto-assign.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Auto Assign
2+
on:
3+
issues:
4+
types: [opened]
5+
pull_request:
6+
types: [opened]
7+
jobs:
8+
run:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
issues: write
12+
pull-requests: write
13+
steps:
14+
- name: 'Auto-assign issue'
15+
uses: pozil/auto-assign-issue@v1
16+
with:
17+
repo-token: ${{ secrets.GITHUB_TOKEN }}
18+
assignees: Arash-Sabet
19+
numOfAssignee: 1

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ This library brings in Microsoft's dependency injection container to Xunit by le
1111
## Getting started
1212

1313
### Nuget package
14+
1415
First add the following [nuget package](https://www.nuget.org/packages/Xunit.Microsoft.DependencyInjection/) to your Xunit project:
1516

16-
```
17+
```ps
1718
Install-Package Xunit.Microsoft.DependencyInjection
1819
```
1920

@@ -28,7 +29,16 @@ protected abstract void AddServices(IServiceCollection services, IConfiguration
2829

2930
`GetConfigurationFiles(...)` method returns a collection of the configuration files in your Xunit test project to the framework. `AddServices(...)` method must be used to wire up the implemented services.
3031

32+
#### Secret manager
33+
34+
[Secret manage](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&tabs=windows#how-the-secret-manager-tool-works) is a great tool to store credentials, api keys and other secret information for development purpose. This library has started supporting user secrets from version 8.2.0 onwards. To utilize user secrets in your tests, simply override the `virtual` method below from `TestBedFixture` class:
35+
36+
```csharp
37+
protected override void AddUserSecrets(IConfigurationBuilder configurationBuilder);
38+
```
39+
3140
### Access the wired up services
41+
3242
There are two method that you can use to access the wired up service depending on your context:
3343

3444
```csharp
@@ -43,16 +53,19 @@ public AsyncServiceScope GetAsyncScope<T>(ITestOutputHelper testOutputHelper)
4353
```
4454

4555
### Accessing the keyed wired up services in .NET 8.0
56+
4657
You can call the following method to access the keyed already-wired up services:
4758

4859
```csharp
4960
T? GetKeyedService<T>([DisallowNull] string key, ITestOutputHelper testOutputHelper);
5061
```
5162

5263
### Adding custom logging provider
64+
5365
Test developers can add their own desired logger provider by overriding ```AddLoggingProvider(...)``` virtual method defined in ```TestBedFixture``` class.
5466

5567
### Preparing Xunit test classes
68+
5669
Your Xunit test class must be derived from ```Xunit.Microsoft.DependencyInjection.Abstracts.TestBed<T>``` class where ```T``` should be your fixture class derived from ```TestBedFixture```.
5770

5871
Also, the test class should be decorated by the following attribute:
@@ -93,6 +106,7 @@ public IConfigurationBuilder ConfigurationBuilder { get; private set; }
93106
* [Digital Silo](https://digitalsilo.io/)'s unit tests and integration tests are using this library.
94107

95108
### One more thing
109+
96110
Do not forget to include the following nuget packages to your Xunit project:
97111

98112
* Microsoft.Extensions.DependencyInjection

azure-pipelines.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
variables:
22
Major: 8
3-
Minor: 1
4-
Revision: 1
3+
Minor: 2
4+
Revision: 0
55
BuildConfiguration: Release
66

77
name: $(Major).$(Minor).$(Revision)

examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Fixtures/TestProjectFixture.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ protected override void AddServices(IServiceCollection services, IConfiguration?
77
.AddTransient<ICalculator, Calculator>()
88
.AddKeyedTransient<ICarMaker, Porsche>("Porsche")
99
.AddKeyedTransient<ICarMaker, Toyota>("Toyota")
10-
.Configure<Options>(config => configuration?.GetSection("Options").Bind(config));
10+
.Configure<Options>(config => configuration?.GetSection("Options").Bind(config))
11+
.Configure<SecretValues>(config => configuration?.GetSection(nameof(SecretValues)).Bind(config));
1112

1213
protected override ValueTask DisposeAsyncCore()
1314
=> new();
@@ -16,4 +17,7 @@ protected override IEnumerable<TestAppSettings> GetTestAppSettings()
1617
{
1718
yield return new() { Filename = "appsettings.json", IsOptional = false };
1819
}
20+
21+
protected override void AddUserSecrets(IConfigurationBuilder configurationBuilder)
22+
=> configurationBuilder.AddUserSecrets<TestProjectFixture>();
1923
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Xunit.Microsoft.DependencyInjection.ExampleTests;
2+
3+
public record SecretValues
4+
{
5+
public string? Secret1 { get; set; }
6+
public string? Secret2 { get; set; }
7+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+

2+
using Microsoft.Extensions.Options;
3+
4+
namespace Xunit.Microsoft.DependencyInjection.ExampleTests;
5+
6+
public class UserSecretTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : TestBed<TestProjectFixture>(testOutputHelper, fixture)
7+
{
8+
[Fact]
9+
public void TestSecretValues()
10+
{
11+
/*
12+
* TODO: Create a user secret entry like the following payload in user secrets and remove the same from appsettings.json file:
13+
*
14+
* "SecretValues": {
15+
* "Secret1": "secret1value",
16+
* "Secret2": "secret2value"
17+
* }
18+
*/
19+
var secretValues = _fixture.GetService<IOptions<SecretValues>>(_testOutputHelper)!.Value;
20+
Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty);
21+
Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty);
22+
}
23+
}

examples/Xunit.Microsoft.DependencyInjection.ExampleTests/Xunit.Microsoft.DependencyInjection.ExampleTests.csproj

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,27 @@
66
<IsPackable>false</IsPackable>
77
<Nullable>enable</Nullable>
88
<ImplicitUsings>enable</ImplicitUsings>
9+
<UserSecretsId>59bdc82c-5628-47c8-a5ec-3630c3a2bc45</UserSecretsId>
910
</PropertyGroup>
1011

1112
<ItemGroup>
13+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
1214
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
13-
<PackageReference Include="xunit" Version="2.8.1" />
14-
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
15+
<PackageReference Include="xunit" Version="2.9.0" />
16+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
1517
<PrivateAssets>all</PrivateAssets>
1618
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1719
</PackageReference>
20+
<PackageReference Include="coverlet.collector" Version="6.0.2">
21+
<PrivateAssets>all</PrivateAssets>
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
</PackageReference>
1824
<PackageReference Include="coverlet.collector" Version="6.0.2" />
1925
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
2026
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
2127
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
22-
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
23-
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
28+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
29+
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
2430
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
2531
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
2632
</ItemGroup>
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
2-
"Options": {
3-
"Rate" : 10
4-
}
2+
"Options": {
3+
"Rate": 10
4+
},
5+
"SecretValues": {
6+
"Secret1": "StoreSecret1InUserSecrets",
7+
"Secret2": "StoreSecret2InUserSecrets"
8+
}
59
}

src/Abstracts/TestBedFixture.cs

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ protected TestBedFixture()
1414
{
1515
_services = new ServiceCollection();
1616
ConfigurationBuilder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory());
17+
AddUserSecrets(ConfigurationBuilder);
1718
Configuration = GetConfigurationRoot();
1819
_servicesAdded = false;
1920
}
@@ -30,9 +31,10 @@ public ServiceProvider GetServiceProvider(ITestOutputHelper testOutputHelper)
3031
if(!_servicesAdded)
3132
{
3233
AddServices(_services, Configuration);
34+
_services.AddLogging(loggingBuilder => AddLoggingProvider(loggingBuilder, new OutputLoggerProvider(testOutputHelper)));
35+
_services.AddOptions();
3336
_servicesAdded = true;
3437
}
35-
_services.AddLogging(loggingBuilder => AddLoggingProvider(loggingBuilder, new OutputLoggerProvider(testOutputHelper)));
3638
return _serviceProvider = _services.BuildServiceProvider();
3739
}
3840

@@ -55,12 +57,38 @@ public AsyncServiceScope GetAsyncScope(ITestOutputHelper testOutputHelper)
5557
public T? GetKeyedService<T>([DisallowNull] string key, ITestOutputHelper testOutputHelper)
5658
=> GetServiceProvider(testOutputHelper).GetKeyedService<T>(key);
5759

60+
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
61+
// ~AbstractDependencyInjectionFixture()
62+
// {
63+
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
64+
// Dispose(disposing: false);
65+
// }
66+
67+
public void Dispose()
68+
{
69+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
70+
Dispose(disposing: true);
71+
GC.SuppressFinalize(this);
72+
}
73+
74+
public async ValueTask DisposeAsync()
75+
{
76+
if (!_disposedAsync)
77+
{
78+
await DisposeAsyncCore();
79+
Dispose();
80+
_disposedAsync = true;
81+
}
82+
}
83+
5884
protected abstract void AddServices(IServiceCollection services, IConfiguration? configuration);
5985
protected abstract IEnumerable<TestAppSettings> GetTestAppSettings();
6086

6187
protected virtual ILoggingBuilder AddLoggingProvider(ILoggingBuilder loggingBuilder, ILoggerProvider loggerProvider)
6288
=> loggingBuilder.AddProvider(loggerProvider);
6389

90+
protected virtual void AddUserSecrets(IConfigurationBuilder configurationBuilder) { }
91+
6492
private IConfigurationRoot? GetConfigurationRoot()
6593
{
6694
var testAppSettings = GetTestAppSettings();
@@ -100,29 +128,5 @@ protected virtual void Dispose(bool disposing)
100128
}
101129
}
102130

103-
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
104-
// ~AbstractDependencyInjectionFixture()
105-
// {
106-
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
107-
// Dispose(disposing: false);
108-
// }
109-
110-
public void Dispose()
111-
{
112-
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
113-
Dispose(disposing: true);
114-
GC.SuppressFinalize(this);
115-
}
116-
117-
public async ValueTask DisposeAsync()
118-
{
119-
if (!_disposedAsync)
120-
{
121-
await DisposeAsyncCore();
122-
Dispose();
123-
_disposedAsync = true;
124-
}
125-
}
126-
127131
protected abstract ValueTask DisposeAsyncCore();
128132
}

src/Logging/OutputLogger.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ public bool IsEnabled(LogLevel logLevel)
1818

1919
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception, string> formatter)
2020
{
21-
if (exception is not null)
21+
try
2222
{
23-
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {formatter(state, exception)} :: {DateTime.Now}");
23+
if (exception is not null)
24+
{
25+
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {formatter(state, exception)} :: {DateTime.Now}");
26+
}
27+
else
28+
{
29+
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {state} :: {DateTime.Now}");
30+
}
2431
}
25-
else
32+
catch
2633
{
27-
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {state} :: {DateTime.Now}");
34+
//Ignore
2835
}
2936
}
3037
}

src/Xunit.Microsoft.DependencyInjection.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
1111
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
1212
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
13-
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
1414
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
15-
<PackageReference Include="xunit.core" Version="2.8.1" />
15+
<PackageReference Include="xunit.core" Version="2.9.0" />
1616
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
1717
</ItemGroup>
1818
<ItemGroup>

0 commit comments

Comments
 (0)