Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions docs/xunit.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,21 @@ public class EnvironmentInitializer : IFrameworkInitializer
```

**Parameterized tests with data from the IServiceProvider:**
Create a static method that accepts IServiceProvider and returns IEnumerable<object>. Pass that method to ServiceProviderMemberDataAttribute to use it as data source for Theory.
Create a instance method or property that returns IEnumerable<object>. Pass that method to ServiceProviderMemberDataAttribute to use it as data source for Theory.

```
[Bss.Testing.Xunit.Sdk.Theory]
[ServiceProviderMemberData(nameof(GetMemberData))]
public void GetDataFromServiceProvider(FullSecurityRole role) => Assert.NotEmpty(role.Name);

protected static IEnumerable<object> GetMemberData(IServiceProvider serviceProvider) =>
serviceProvider.GetRequiredService<ISecurityRoleSource>().SecurityRoles.Select(x => new [] { x });
protected IEnumerable<object> GetMemberData() =>
this.ServiceProvider.GetRequiredService<ISecurityRoleSource>().SecurityRoles.Select(x => new [] { x });
```

```
[Bss.Testing.Xunit.Sdk.Theory]
[ServiceProviderMemberData(nameof(GetMemberData))]
public void GetDataFromServiceProvider(FullSecurityRole role) => Assert.NotEmpty(role.Name);

protected IEnumerable<object> GetMemberData => this.ServiceProvider.GetRequiredService<ISecurityRoleSource>().SecurityRoles.Select(x => new [] { x });
```
Original file line number Diff line number Diff line change
Expand Up @@ -5,86 +5,101 @@

using System.Reflection;

using Microsoft.Extensions.DependencyInjection;

namespace Bss.Testing.Xunit.Sdk;

[DataDiscoverer("Bss.Testing.Xunit.Sdk.ServiceProviderMemberDataDiscoverer", "Bss.Testing.Xunit")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class ServiceProviderMemberDataAttribute(string methodName) : DataAttribute
public class ServiceProviderMemberDataAttribute(string methodOrPropertyName) : DataAttribute

Check warning on line 14 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'MemberType' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 14 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'MemberType' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
{
public Type? MemberType { get; set; }
public Type MemberType { get; set; }

string MemberName { get; set; } = methodName;
string MemberName { get; set; } = methodOrPropertyName;

public override IEnumerable<object[]>? GetData(MethodInfo testMethod) => null;
public override IEnumerable<object[]> GetData(MethodInfo testMethod) => null;

Check warning on line 20 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.

Check warning on line 20 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.

public IEnumerable<object[]>? GetData(MethodInfo testMethod, IServiceProvider? serviceProvider)
public IEnumerable<object[]> GetData(MethodInfo testMethod, IServiceProvider serviceProvider)
{
var type = this.MemberType ?? testMethod.DeclaringType;
if (type == null)
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
"Could not find type {0}",
type?.FullName)
);
}
var accessor = this.GetMethodAccessor(type, serviceProvider)

Check warning on line 25 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'type' in 'Func<object?>? ServiceProviderMemberDataAttribute.GetMethodAccessor(Type type, IServiceProvider serviceProvider)'.

Check warning on line 25 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'type' in 'Func<object?>? ServiceProviderMemberDataAttribute.GetMethodAccessor(Type type, IServiceProvider serviceProvider)'.
?? this.GetPropertyAccessor(type, serviceProvider);

var accessor = this.GetMethodAccessor(type, serviceProvider);
if (accessor == null)
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
"Could not find public static method named '{0}' on {1}{2}",
"Could not find parameterless method or property named '{0}' on {1} provided in ServiceProviderMemberDataAttribute",
this.MemberName,
type?.FullName,
" with parameter types: IServiceProvider")
type?.FullName)
);
}

var obj = accessor();
if (obj == null)
{
return (IEnumerable<object[]>) Array.Empty<object>();
return null;

Check warning on line 42 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
}

if (obj is not IEnumerable dataItems)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Method {0} on {1} did not return IEnumerable", this.MemberName, type?.FullName));
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Method/property {0} on {1} did not return IEnumerable", this.MemberName, type?.FullName));
}

return dataItems.Cast<object>().Select(item => this.ConvertDataItem(testMethod, item))!;
return dataItems.Cast<object>().Select(item => this.ConvertDataItem(testMethod, item));

Check warning on line 50 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of reference types in value of type 'IEnumerable<object[]?>' doesn't match target type 'IEnumerable<object[]>'.
}

protected Func<object>? GetMethodAccessor(Type type, IServiceProvider? serviceProvider)
private Func<object?>? GetMethodAccessor(Type type, IServiceProvider serviceProvider)
{
MethodInfo? methodInfo = null;
for (var reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType)
{
var runtimeMethodsWithGivenName = reflectionType.GetRuntimeMethods()
.Where(m => m.Name == this.MemberName)
.ToArray();
methodInfo = reflectionType
.GetRuntimeMethods()
.FirstOrDefault(m => m.Name == this.MemberName);
if (methodInfo != null)
{
break;
}
}

if (methodInfo == null)
{
return null;
}

methodInfo = runtimeMethodsWithGivenName
.FirstOrDefault(m => m.GetParameters()
.Count(x => x.ParameterType.IsAssignableTo(typeof(IServiceProvider))) == 1);
var @object = ActivatorUtilities.CreateInstance(serviceProvider, type);

if (methodInfo != null)
return () => methodInfo.Invoke(@object, null);
}

private Func<object?>? GetPropertyAccessor(Type type, IServiceProvider serviceProvider)
{
PropertyInfo? propertyInfo = null;
for (var reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType)
{
propertyInfo = reflectionType
.GetProperties()
.FirstOrDefault(m => m.Name == this.MemberName);

if (propertyInfo != null)
{
break;
}
}

if (methodInfo == null || !methodInfo.IsStatic)
if (propertyInfo == null)
{
return null;
}

return () => methodInfo.Invoke(null, [serviceProvider])!;
var @object = ActivatorUtilities.CreateInstance(serviceProvider, type);

return () => propertyInfo.GetValue(@object);
}

protected object[]? ConvertDataItem(MethodInfo testMethod, object? item)
private object[]? ConvertDataItem(MethodInfo testMethod, object? item)
{
if (item == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
public class ServiceProviderMemberDataDiscoverer : IDataDiscoverer
{
public IEnumerable<object[]> GetData(IAttributeInfo dataAttribute, IMethodInfo testMethod) =>
throw new ArgumentException($"ServiceProviderMemberDataDiscoverer cannot be used as discoverer for any *DataAttribute other than ServiceProviderMemberDataAttribute.");
throw new ArgumentException("ServiceProviderMemberDataDiscoverer cannot be used as discoverer for any *DataAttribute other than ServiceProviderMemberDataAttribute.");

public IEnumerable<object[]>? GetData(IAttributeInfo dataAttribute, IMethodInfo testMethod, IServiceProvider? serviceProvider)
{
if (serviceProvider == null)
{
throw new ArgumentException($"ServiceProvider cannot be null for {nameof(dataAttribute)}");
}


if (dataAttribute is not IReflectionAttributeInfo reflectionDataAttribute
|| testMethod is not IReflectionMethodInfo reflectionTestMethod)
{
Expand All @@ -30,7 +36,7 @@
var reflectionTestMethodType = reflectionTestMethod.Type as IReflectionTypeInfo;
if (attribute is { MemberType: null })
{
attribute.MemberType = reflectionTestMethodType?.Type;

Check warning on line 39 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataDiscoverer.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.

Check warning on line 39 in xunit/src/Bss.Testing.Xunit/Sdk/ServiceProviderMemberDataDiscoverer.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
}

return attribute.GetData(reflectionTestMethod.MethodInfo, serviceProvider);
Expand Down
2 changes: 0 additions & 2 deletions xunit/src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>false</GenerateDocumentationFile>

<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Release'">
Expand Down