Skip to content

Commit 53ffd69

Browse files
authored
refactor: Use AutoDomainData attribute instead of directly mocking with NSubstitute (#648)
Add a custom `AutoDomainData` extension of [`AutoDataAttribute`](https://github.com/AutoFixture/AutoFixture/blob/master/Src/AutoFixture.xUnit2/AutoDataAttribute.cs), that uses [NSubstitute](https://nsubstitute.github.io) and allows for domain-specific [customizations](https://autofixture.github.io/docs/fixture-customization/). In order to apply a customization for all tests in a project, add a class implementing the `IAutoDataCustomization` interface. This customization is then applied to all tests using the `[AutoDomainData]` attribute. Additionally this attribute has a property `CustomizeWith` which can point to a class implementing the [`ICustomization`](https://autofixture.github.io/docs/fixture-customization/) interface and which is then applied to this test only.
1 parent 8d80da4 commit 53ffd69

File tree

6 files changed

+132
-14
lines changed

6 files changed

+132
-14
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
2121
</ItemGroup>
2222
<ItemGroup>
23+
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="4.18.1" />
2324
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
2425
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
2526
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
2627
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
2728
<PackageVersion Include="xunit" Version="2.9.0" />
2829
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
2930
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
30-
<PackageVersion Include="NSubstitute" Version="5.1.0" />
3131
<PackageVersion Include="PublicApiGenerator" Version="11.1.0" />
3232
<PackageVersion Include="NUnit" Version="4.2.2" />
3333
<PackageVersion Include="NUnit.Analyzers" Version="4.3.0" />
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using AutoFixture;
2+
using AutoFixture.Xunit2;
3+
using AutoFixture.AutoNSubstitute;
4+
using System;
5+
using System.Linq;
6+
using System.Collections.Generic;
7+
8+
namespace Testably.Abstractions.TestHelpers;
9+
10+
/// <summary>
11+
/// Extension of <see cref="AutoDataAttribute"/> that uses applies domain-specific customizations.
12+
/// </summary>
13+
public class AutoDomainDataAttribute : AutoDataAttribute
14+
{
15+
private Type? _customizeWith;
16+
private readonly FixtureFactory _fixtureFactory;
17+
18+
/// <summary>
19+
/// Extension of <see cref="AutoDataAttribute"/> that uses applies domain-specific customizations.
20+
/// </summary>
21+
public AutoDomainDataAttribute() : this(new FixtureFactory())
22+
{
23+
}
24+
25+
private AutoDomainDataAttribute(FixtureFactory fixtureFactory)
26+
: base(fixtureFactory.GetFixtureFactory)
27+
{
28+
_fixtureFactory = fixtureFactory;
29+
}
30+
31+
/// <summary>
32+
/// Adds an additional <see cref="ICustomization"/> that is applied for only this test.
33+
/// </summary>
34+
public Type? CustomizeWith
35+
{
36+
get
37+
{
38+
return _customizeWith;
39+
}
40+
set
41+
{
42+
_customizeWith = value;
43+
_fixtureFactory.CustomizeWith(value);
44+
}
45+
}
46+
47+
private sealed class FixtureFactory
48+
{
49+
private ICustomization? _customizeWith;
50+
private static Lazy<ICustomization[]> _domainCustomisation { get; } = new(Initialize);
51+
52+
public IFixture GetFixtureFactory()
53+
{
54+
var fixture = new Fixture();
55+
fixture.Customize(new AutoNSubstituteCustomization());
56+
foreach (var domainCustomization in _domainCustomisation.Value)
57+
{
58+
domainCustomization.Customize(fixture);
59+
}
60+
if (_customizeWith != null)
61+
{
62+
fixture.Customize(_customizeWith);
63+
}
64+
return fixture;
65+
}
66+
67+
public void CustomizeWith(Type? type)
68+
{
69+
Type customizationInterface = typeof(ICustomization);
70+
if (type != null &&
71+
customizationInterface.IsAssignableFrom(type))
72+
{
73+
try
74+
{
75+
_customizeWith = (ICustomization?)Activator.CreateInstance(type);
76+
}
77+
catch (Exception ex)
78+
{
79+
throw new InvalidOperationException(
80+
$"Could not instantiate customization with '{type.Name}'!", ex);
81+
}
82+
}
83+
}
84+
85+
private static ICustomization[] Initialize()
86+
{
87+
List<ICustomization> domainCustomizations = new();
88+
Type autoDataCustomizationInterface = typeof(IAutoDataCustomization);
89+
foreach (Type type in AppDomain.CurrentDomain.GetAssemblies()
90+
.SelectMany(a => a.GetTypes())
91+
.Where(x => x.IsClass)
92+
.Where(autoDataCustomizationInterface.IsAssignableFrom))
93+
{
94+
try
95+
{
96+
IAutoDataCustomization? domainCustomization = (IAutoDataCustomization?)Activator.CreateInstance(type);
97+
if (domainCustomization != null)
98+
{
99+
domainCustomizations.Add(domainCustomization);
100+
}
101+
}
102+
catch (Exception ex)
103+
{
104+
throw new InvalidOperationException(
105+
$"Could not instantiate auto data customization '{type.Name}'!", ex);
106+
}
107+
}
108+
return domainCustomizations.ToArray();
109+
}
110+
}
111+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using AutoFixture;
2+
3+
namespace Testably.Abstractions.TestHelpers;
4+
5+
/// <summary>
6+
/// Marks customizations of <see cref="IFixture"/> that should always be applied when using the <see cref="AutoDomainDataAttribute"/>.
7+
/// </summary>
8+
public interface IAutoDataCustomization : ICustomization
9+
{
10+
}

Tests/Helpers/Testably.Abstractions.TestHelpers/Testably.Abstractions.TestHelpers.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
</ItemGroup>
2020

2121
<ItemGroup>
22-
<PackageReference Remove="AutoFixture.Xunit2" />
22+
<PackageReference Include="AutoFixture.AutoNSubstitute" />
23+
<PackageReference Include="AutoFixture.Xunit2" />
2324
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
2425
<PackageReference Remove="xunit" />
2526
<PackageReference Remove="xunit.runner.visualstudio" />

Tests/Testably.Abstractions.AccessControl.Tests/Internal/AccessControlHelperTests.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
using NSubstitute;
2-
using System.IO;
1+
using System.IO;
32
using Testably.Abstractions.Helpers;
43

54
namespace Testably.Abstractions.AccessControl.Tests.Internal;
65

76
public sealed class AccessControlHelperTests
87
{
9-
[Fact]
10-
public void GetExtensibilityOrThrow_CustomDirectoryInfo_ShouldThrowNotSupportedException()
8+
[Theory]
9+
[AutoDomainData]
10+
public void GetExtensibilityOrThrow_CustomDirectoryInfo_ShouldThrowNotSupportedException(IDirectoryInfo sut)
1111
{
12-
IDirectoryInfo? sut = Substitute.For<IDirectoryInfo>();
13-
1412
Exception? exception = Record.Exception(() =>
1513
{
1614
sut.GetExtensibilityOrThrow();
@@ -22,11 +20,10 @@ public void GetExtensibilityOrThrow_CustomDirectoryInfo_ShouldThrowNotSupportedE
2220
.Contain(sut.GetType().Name);
2321
}
2422

25-
[Fact]
26-
public void GetExtensibilityOrThrow_CustomFileInfo_ShouldThrowNotSupportedException()
23+
[Theory]
24+
[AutoDomainData]
25+
public void GetExtensibilityOrThrow_CustomFileInfo_ShouldThrowNotSupportedException(IFileInfo sut)
2726
{
28-
IFileInfo? sut = Substitute.For<IFileInfo>();
29-
3027
Exception? exception = Record.Exception(() =>
3128
{
3229
sut.GetExtensibilityOrThrow();
@@ -116,5 +113,5 @@ public void ThrowIfMissing_MissingFileInfo_ShouldThrowFileNotFoundException()
116113
exception!.Message.Should().Contain($"'{sut.FullName}'");
117114
}
118115

119-
private class CustomFileSystemStream() : FileSystemStream(Null, ".", false);
116+
private sealed class CustomFileSystemStream() : FileSystemStream(Null, ".", false);
120117
}

Tests/Testably.Abstractions.AccessControl.Tests/Testably.Abstractions.AccessControl.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
<ItemGroup>
1515
<ProjectReference Include="..\Helpers\Testably.Abstractions.Tests.SourceGenerator\Testably.Abstractions.Tests.SourceGenerator.csproj" OutputItemType="Analyzer" />
16-
<PackageReference Include="NSubstitute" />
1716
</ItemGroup>
1817

1918
<PropertyGroup>

0 commit comments

Comments
 (0)