Skip to content

Commit

Permalink
refactor: Use AutoDomainData attribute instead of directly mocking …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
vbreuss authored Sep 14, 2024
1 parent 8d80da4 commit 53ffd69
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="xunit" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="PublicApiGenerator" Version="11.1.0" />
<PackageVersion Include="NUnit" Version="4.2.2" />
<PackageVersion Include="NUnit.Analyzers" Version="4.3.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using AutoFixture;
using AutoFixture.Xunit2;
using AutoFixture.AutoNSubstitute;
using System;
using System.Linq;
using System.Collections.Generic;

namespace Testably.Abstractions.TestHelpers;

/// <summary>
/// Extension of <see cref="AutoDataAttribute"/> that uses applies domain-specific customizations.
/// </summary>
public class AutoDomainDataAttribute : AutoDataAttribute
{
private Type? _customizeWith;
private readonly FixtureFactory _fixtureFactory;

/// <summary>
/// Extension of <see cref="AutoDataAttribute"/> that uses applies domain-specific customizations.
/// </summary>
public AutoDomainDataAttribute() : this(new FixtureFactory())
{
}

private AutoDomainDataAttribute(FixtureFactory fixtureFactory)
: base(fixtureFactory.GetFixtureFactory)
{
_fixtureFactory = fixtureFactory;
}

/// <summary>
/// Adds an additional <see cref="ICustomization"/> that is applied for only this test.
/// </summary>
public Type? CustomizeWith
{
get
{
return _customizeWith;
}
set
{
_customizeWith = value;
_fixtureFactory.CustomizeWith(value);
}
}

private sealed class FixtureFactory
{
private ICustomization? _customizeWith;
private static Lazy<ICustomization[]> _domainCustomisation { get; } = new(Initialize);

public IFixture GetFixtureFactory()
{
var fixture = new Fixture();
fixture.Customize(new AutoNSubstituteCustomization());
foreach (var domainCustomization in _domainCustomisation.Value)
{
domainCustomization.Customize(fixture);
}
if (_customizeWith != null)
{
fixture.Customize(_customizeWith);
}
return fixture;
}

public void CustomizeWith(Type? type)
{
Type customizationInterface = typeof(ICustomization);
if (type != null &&
customizationInterface.IsAssignableFrom(type))
{
try
{
_customizeWith = (ICustomization?)Activator.CreateInstance(type);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Could not instantiate customization with '{type.Name}'!", ex);
}
}
}

private static ICustomization[] Initialize()
{
List<ICustomization> domainCustomizations = new();
Type autoDataCustomizationInterface = typeof(IAutoDataCustomization);
foreach (Type type in AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(x => x.IsClass)
.Where(autoDataCustomizationInterface.IsAssignableFrom))
{
try
{
IAutoDataCustomization? domainCustomization = (IAutoDataCustomization?)Activator.CreateInstance(type);
if (domainCustomization != null)
{
domainCustomizations.Add(domainCustomization);
}
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Could not instantiate auto data customization '{type.Name}'!", ex);
}
}
return domainCustomizations.ToArray();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using AutoFixture;

namespace Testably.Abstractions.TestHelpers;

/// <summary>
/// Marks customizations of <see cref="IFixture"/> that should always be applied when using the <see cref="AutoDomainDataAttribute"/>.
/// </summary>
public interface IAutoDataCustomization : ICustomization
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Remove="AutoFixture.Xunit2" />
<PackageReference Include="AutoFixture.AutoNSubstitute" />
<PackageReference Include="AutoFixture.Xunit2" />
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
using NSubstitute;
using System.IO;
using System.IO;
using Testably.Abstractions.Helpers;

namespace Testably.Abstractions.AccessControl.Tests.Internal;

public sealed class AccessControlHelperTests
{
[Fact]
public void GetExtensibilityOrThrow_CustomDirectoryInfo_ShouldThrowNotSupportedException()
[Theory]
[AutoDomainData]
public void GetExtensibilityOrThrow_CustomDirectoryInfo_ShouldThrowNotSupportedException(IDirectoryInfo sut)
{
IDirectoryInfo? sut = Substitute.For<IDirectoryInfo>();

Exception? exception = Record.Exception(() =>
{
sut.GetExtensibilityOrThrow();
Expand All @@ -22,11 +20,10 @@ public void GetExtensibilityOrThrow_CustomDirectoryInfo_ShouldThrowNotSupportedE
.Contain(sut.GetType().Name);
}

[Fact]
public void GetExtensibilityOrThrow_CustomFileInfo_ShouldThrowNotSupportedException()
[Theory]
[AutoDomainData]
public void GetExtensibilityOrThrow_CustomFileInfo_ShouldThrowNotSupportedException(IFileInfo sut)
{
IFileInfo? sut = Substitute.For<IFileInfo>();

Exception? exception = Record.Exception(() =>
{
sut.GetExtensibilityOrThrow();
Expand Down Expand Up @@ -116,5 +113,5 @@ public void ThrowIfMissing_MissingFileInfo_ShouldThrowFileNotFoundException()
exception!.Message.Should().Contain($"'{sut.FullName}'");
}

private class CustomFileSystemStream() : FileSystemStream(Null, ".", false);
private sealed class CustomFileSystemStream() : FileSystemStream(Null, ".", false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

<ItemGroup>
<ProjectReference Include="..\Helpers\Testably.Abstractions.Tests.SourceGenerator\Testably.Abstractions.Tests.SourceGenerator.csproj" OutputItemType="Analyzer" />
<PackageReference Include="NSubstitute" />
</ItemGroup>

<PropertyGroup>
Expand Down

0 comments on commit 53ffd69

Please sign in to comment.