Skip to content

Commit

Permalink
Merge pull request #263 from Umplify/262-support-user-secrets-in-the-…
Browse files Browse the repository at this point in the history
…test-fixture

262 support user secrets in the test fixture
  • Loading branch information
Arash-Sabet authored Jul 9, 2024
2 parents fd650ad + a7d4d07 commit 9e5a0ad
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 42 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/auto-assign.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Auto Assign
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
assignees: Arash-Sabet
numOfAssignee: 1
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ This library brings in Microsoft's dependency injection container to Xunit by le
## Getting started

### Nuget package

First add the following [nuget package](https://www.nuget.org/packages/Xunit.Microsoft.DependencyInjection/) to your Xunit project:

```
```ps
Install-Package Xunit.Microsoft.DependencyInjection
```

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

`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.

#### Secret manager

[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:

```csharp
protected override void AddUserSecrets(IConfigurationBuilder configurationBuilder);
```

### Access the wired up services

There are two method that you can use to access the wired up service depending on your context:

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

### Accessing the keyed wired up services in .NET 8.0

You can call the following method to access the keyed already-wired up services:

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

### Adding custom logging provider

Test developers can add their own desired logger provider by overriding ```AddLoggingProvider(...)``` virtual method defined in ```TestBedFixture``` class.

### Preparing Xunit test classes

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```.

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

### One more thing

Do not forget to include the following nuget packages to your Xunit project:

* Microsoft.Extensions.DependencyInjection
Expand Down
4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
variables:
Major: 8
Minor: 1
Revision: 1
Minor: 2
Revision: 0
BuildConfiguration: Release

name: $(Major).$(Minor).$(Revision)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ protected override void AddServices(IServiceCollection services, IConfiguration?
.AddTransient<ICalculator, Calculator>()
.AddKeyedTransient<ICarMaker, Porsche>("Porsche")
.AddKeyedTransient<ICarMaker, Toyota>("Toyota")
.Configure<Options>(config => configuration?.GetSection("Options").Bind(config));
.Configure<Options>(config => configuration?.GetSection("Options").Bind(config))
.Configure<SecretValues>(config => configuration?.GetSection(nameof(SecretValues)).Bind(config));

protected override ValueTask DisposeAsyncCore()
=> new();
Expand All @@ -16,4 +17,7 @@ protected override IEnumerable<TestAppSettings> GetTestAppSettings()
{
yield return new() { Filename = "appsettings.json", IsOptional = false };
}

protected override void AddUserSecrets(IConfigurationBuilder configurationBuilder)
=> configurationBuilder.AddUserSecrets<TestProjectFixture>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Xunit.Microsoft.DependencyInjection.ExampleTests;

public record SecretValues
{
public string? Secret1 { get; set; }
public string? Secret2 { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

using Microsoft.Extensions.Options;

namespace Xunit.Microsoft.DependencyInjection.ExampleTests;

public class UserSecretTests(ITestOutputHelper testOutputHelper, TestProjectFixture fixture) : TestBed<TestProjectFixture>(testOutputHelper, fixture)
{
[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<IOptions<SecretValues>>(_testOutputHelper)!.Value;
Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty);
Assert.NotEmpty(secretValues?.Secret1 ?? string.Empty);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,27 @@
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>59bdc82c-5628-47c8-a5ec-3630c3a2bc45</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"Options": {
"Rate" : 10
}
"Options": {
"Rate": 10
},
"SecretValues": {
"Secret1": "StoreSecret1InUserSecrets",
"Secret2": "StoreSecret2InUserSecrets"
}
}
54 changes: 29 additions & 25 deletions src/Abstracts/TestBedFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protected TestBedFixture()
{
_services = new ServiceCollection();
ConfigurationBuilder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory());
AddUserSecrets(ConfigurationBuilder);
Configuration = GetConfigurationRoot();
_servicesAdded = false;
}
Expand All @@ -30,9 +31,10 @@ public ServiceProvider GetServiceProvider(ITestOutputHelper testOutputHelper)
if(!_servicesAdded)
{
AddServices(_services, Configuration);
_services.AddLogging(loggingBuilder => AddLoggingProvider(loggingBuilder, new OutputLoggerProvider(testOutputHelper)));
_services.AddOptions();
_servicesAdded = true;
}
_services.AddLogging(loggingBuilder => AddLoggingProvider(loggingBuilder, new OutputLoggerProvider(testOutputHelper)));
return _serviceProvider = _services.BuildServiceProvider();
}

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

// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~AbstractDependencyInjectionFixture()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

public async ValueTask DisposeAsync()
{
if (!_disposedAsync)
{
await DisposeAsyncCore();
Dispose();
_disposedAsync = true;
}
}

protected abstract void AddServices(IServiceCollection services, IConfiguration? configuration);
protected abstract IEnumerable<TestAppSettings> GetTestAppSettings();

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

protected virtual void AddUserSecrets(IConfigurationBuilder configurationBuilder) { }

private IConfigurationRoot? GetConfigurationRoot()
{
var testAppSettings = GetTestAppSettings();
Expand Down Expand Up @@ -100,29 +128,5 @@ protected virtual void Dispose(bool disposing)
}
}

// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~AbstractDependencyInjectionFixture()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

public async ValueTask DisposeAsync()
{
if (!_disposedAsync)
{
await DisposeAsyncCore();
Dispose();
_disposedAsync = true;
}
}

protected abstract ValueTask DisposeAsyncCore();
}
15 changes: 11 additions & 4 deletions src/Logging/OutputLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,20 @@ public bool IsEnabled(LogLevel logLevel)

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception, string> formatter)
{
if (exception is not null)
try
{
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {formatter(state, exception)} :: {DateTime.Now}");
if (exception is not null)
{
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {formatter(state, exception)} :: {DateTime.Now}");
}
else
{
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {state} :: {DateTime.Now}");
}
}
else
catch
{
_testOutputHelper.WriteLine($"{logLevel} - Category: {_categoryName} : {state} :: {DateTime.Now}");
//Ignore
}
}
}
4 changes: 2 additions & 2 deletions src/Xunit.Microsoft.DependencyInjection.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="xunit.core" Version="2.8.1" />
<PackageReference Include="xunit.core" Version="2.9.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
Expand Down

0 comments on commit 9e5a0ad

Please sign in to comment.