Skip to content

Commit 51348a3

Browse files
committed
Support required property on test class.
1 parent 93f998b commit 51348a3

File tree

5 files changed

+191
-4
lines changed

5 files changed

+191
-4
lines changed

src/PackAllProject.ps1

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
function FindMSBuild () {
2+
if ($null -eq $env:OS) {
3+
try {
4+
return (Get-Command msbuild).Source;
5+
}
6+
catch {
7+
return $null;
8+
}
9+
}
10+
11+
foreach ($program in ("C:\Program Files", "D:\Program Files")) {
12+
foreach ($vs in (Get-ChildItem ($program + "\Microsoft Visual Studio"))) {
13+
foreach ($vsv in (Get-ChildItem $vs.FullName)) {
14+
if (Test-Path ($vsv.FullName + "\MSBuild\Current\Bin\amd64\MSBuild.exe")) {
15+
return $vsv.FullName + "\MSBuild\Current\Bin\amd64\MSBuild.exe";
16+
}
17+
}
18+
}
19+
}
20+
21+
return $null;
22+
}
23+
24+
if (-not(Test-Path .packages)) {
25+
mkdir .packages
26+
}
27+
28+
foreach ($csproj in (Get-ChildItem -r -filter *.csproj)) {
29+
$dir = "$([System.IO.Path]::GetDirectoryName($csproj.FullName))\bin\Release";
30+
if (Test-Path $dir) {
31+
Remove-Item -Recurse -Force $dir;
32+
}
33+
}
34+
35+
$MSBuild = FindMSBuild
36+
if ($null -eq $MSBuild) {
37+
dotnet build -c Release /p:IsPacking=true ..
38+
}
39+
else {
40+
& "$MSBuild" /r /m /v:m /p:Configuration=Release /p:IsPacking=true ..
41+
}
42+
43+
foreach ($csproj in (Get-ChildItem -r -filter *.csproj)) {
44+
$dir = "$([System.IO.Path]::GetDirectoryName($csproj.FullName))\bin\Release";
45+
46+
if (Test-Path $dir) {
47+
$nupkg = Get-ChildItem "$([System.IO.Path]::GetDirectoryName($csproj.FullName))\bin\Release" |
48+
Where-Object { $_.Name.Endswith(".nupkg") } |
49+
Sort-Object -Property LastWriteTime -Descending |
50+
Select-Object -First 1;
51+
52+
if ($null -ne $nupkg) {
53+
Copy-Item $nupkg.VersionInfo.FIleName (".packages\" + $nupkg.Name) -Force
54+
}
55+
}
56+
}

src/Xunit.DependencyInjection/DependencyInjectionTestInvoker.cs

+47-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,53 @@ public class DependencyInjectionTestInvoker(
2020
: XunitTestInvoker(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments,
2121
beforeAfterAttributes, aggregator, cancellationTokenSource)
2222
{
23-
private static readonly ActivitySource ActivitySource = new("Xunit.DependencyInjection", typeof(DependencyInjectionTestInvoker).Assembly.GetName().Version?.ToString());
24-
private static readonly MethodInfo AsTaskMethod = new Func<ObjectMethodExecutorAwaitable, Task>(AsTask).Method;
23+
private static readonly ActivitySource ActivitySource = new("Xunit.DependencyInjection",
24+
typeof(DependencyInjectionTestInvoker).Assembly.GetName().Version?.ToString());
25+
26+
private static readonly MethodInfo AsTaskMethod;
27+
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> HasRequiredMembers = [];
28+
29+
static DependencyInjectionTestInvoker()
30+
{
31+
var method = AsTask;
32+
33+
AsTaskMethod = method.Method;
34+
}
35+
36+
protected override object? CreateTestClass()
37+
{
38+
var testClassInstance = base.CreateTestClass();
39+
if (testClassInstance == null ||
40+
HasRequiredMembers.GetOrAdd(TestClass, GetRequiredProperties) is not { Length: > 0 } properties)
41+
return testClassInstance;
42+
43+
foreach (var propertyInfo in properties)
44+
{
45+
if (propertyInfo.CanRead)
46+
try
47+
{
48+
if (propertyInfo.GetValue(testClassInstance, null) != null) continue;
49+
}
50+
catch
51+
{
52+
continue;
53+
}
54+
55+
propertyInfo.SetValue(testClassInstance, propertyInfo.PropertyType == typeof(ITestOutputHelper)
56+
? provider.GetRequiredService<ITestOutputHelperAccessor>().Output
57+
: provider.GetRequiredService(propertyInfo.PropertyType));
58+
}
59+
60+
return testClassInstance;
61+
}
62+
63+
private static PropertyInfo[] GetRequiredProperties(Type testClass) =>
64+
!testClass.HasRequiredMemberAttribute() || testClass.GetConstructors().FirstOrDefault(static ci =>
65+
!ci.IsStatic && ci.IsPublic) is not { } ci || ci.HasSetsRequiredMembersAttribute()
66+
? []
67+
: testClass.GetProperties()
68+
.Where(p => p.SetMethod is { IsPublic: true } && p.HasRequiredMemberAttribute())
69+
.ToArray();
2570

2671
/// <inheritdoc/>
2772
protected override async Task<decimal> InvokeTestMethodAsync(object? testClassInstance)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Xunit.DependencyInjection;
2+
3+
internal static class ReflectionExtensions
4+
{
5+
public static bool HasRequiredMemberAttribute(this Type type)
6+
{
7+
for (var currentType = type; currentType is not null && currentType != typeof(object);
8+
currentType = currentType.BaseType)
9+
if (currentType.CustomAttributes.Any(cad =>
10+
cad.AttributeType.FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute"))
11+
return true;
12+
13+
return false;
14+
}
15+
16+
public static bool HasRequiredMemberAttribute(this PropertyInfo propertyInfo) => propertyInfo.CustomAttributes.Any(
17+
cad => cad.AttributeType.FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute");
18+
19+
public static bool HasSetsRequiredMembersAttribute(this ConstructorInfo constructorInfo) =>
20+
constructorInfo.CustomAttributes.Any(cad =>
21+
cad.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute");
22+
}

src/Xunit.DependencyInjection/Xunit.DependencyInjection.csproj

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
Release notes:
88

9+
9.6: Support required property on test class.
910
9.5: Add support for BuildHostApplicationBuilder, fixing TheoryData of T evaluation.
1011
9.4: Add ITestClassOrderer, Support registration ITestCollectionOrderer and ITestCaseOrderer.
1112
9.3: Support xunit 2.8.0.
@@ -23,9 +24,9 @@ Release notes:
2324
8.1: Startup allow static method or class (like Asp.Net Core startup).
2425
8.0: New feature: Support multiple startup.</Description>
2526
<PackageTags>xunit ioc di DependencyInjection test</PackageTags>
26-
<Version>9.5.0</Version>
27+
<Version>9.6.0</Version>
2728
<PackageReleaseNotes>$(Description)</PackageReleaseNotes>
28-
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.RequiresLocationAttribute</PolySharpExcludeGeneratedTypes>
29+
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.RequiresLocationAttribute;System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute</PolySharpExcludeGeneratedTypes>
2930
</PropertyGroup>
3031

3132
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Xunit.DependencyInjection.Test;
4+
5+
public class RequiredMemberTest
6+
{
7+
public required ITestOutputHelper Output { get; set; }
8+
9+
public required IDependency Dependency { get; init; }
10+
11+
[Fact]
12+
public void Test()
13+
{
14+
Assert.NotNull(Output);
15+
Assert.NotNull(Dependency);
16+
}
17+
}
18+
19+
public class SetsRequiredMembersTest
20+
{
21+
private readonly bool _isCtorSet;
22+
private ITestOutputHelper _output;
23+
private readonly IDependency _dependency;
24+
25+
[method: SetsRequiredMembers]
26+
public SetsRequiredMembersTest(ITestOutputHelper output, IDependency dependency)
27+
{
28+
_output = Output = output;
29+
_dependency = Dependency = dependency;
30+
31+
_isCtorSet = true;
32+
}
33+
34+
public required ITestOutputHelper Output
35+
{
36+
get => _output;
37+
set
38+
{
39+
if (_output != null) throw new InvalidOperationException();
40+
41+
_output = value;
42+
}
43+
}
44+
45+
public required IDependency Dependency
46+
{
47+
get => _dependency;
48+
init
49+
{
50+
if (_dependency != null) throw new InvalidOperationException();
51+
52+
_dependency = value;
53+
}
54+
}
55+
56+
[Fact]
57+
public void Test()
58+
{
59+
Assert.True(_isCtorSet);
60+
Assert.NotNull(Output);
61+
Assert.NotNull(Dependency);
62+
}
63+
}

0 commit comments

Comments
 (0)