HenrikJensen.SutFactory 1.0.0-beta1

The ID prefix of this package has been reserved for one of the owners of this package by NuGet.org. Prefix Reserved
This is a prerelease version of HenrikJensen.SutFactory.
dotnet add package HenrikJensen.SutFactory --version 1.0.0-beta1
NuGet\Install-Package HenrikJensen.SutFactory -Version 1.0.0-beta1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HenrikJensen.SutFactory" Version="1.0.0-beta1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add HenrikJensen.SutFactory --version 1.0.0-beta1
#r "nuget: HenrikJensen.SutFactory, 1.0.0-beta1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install HenrikJensen.SutFactory as a Cake Addin
#addin nuget:?package=HenrikJensen.SutFactory&version=1.0.0-beta1&prerelease

// Install HenrikJensen.SutFactory as a Cake Tool
#tool nuget:?package=HenrikJensen.SutFactory&version=1.0.0-beta1&prerelease

A SUT Factory.

This is a System Under Test (SUT) factory designed to eliminate the necessity for hard-coded parameter types during the creation of a SUT instance. It achieves this by integrating a service provider with the NSubstitute mocking library.

In its most basic form, the factory provides a single static method "SystemUnderTest.For" for arranging only the mocks that are relevant to you before the SUT is created. The "SutBuilder" provides more advanced methods and can be used for lambda-less coding or less usual arrangements e.g. testing implementations with multiple constructors.

An example of using the SystemUnderTest.For<T> method:

[Fact]
public void Read_GivenCompatibleDataStore_ReturnsAll()
{
  // arrange
  var sut = SystemUnderTest.For<DataRepository>(SetHappyPath);

  // act
  var result = sut.Read<int>("my integer store");

  // assert
  Assert.Collection(result, item1 => Assert.Equal(10, item1));
}

[Fact]
public void Read_GivenIncompatibleDataStore_Throws()
{
  // arrange
  var sut = SystemUnderTest.For<DataRepository>(arrange =>
  {
    SetHappyPath(arrange);

    // Breaking the happy path!
    // Get the list of data entities and modify it such that
    // a format exception will be thrown.
    var dataEntities = arrange.GetRequiredService<List<DataEntity>>();
    dataEntities[0].Value = "this is not an integer";
  });

  // assert
  Assert.Throws<FormatException>(() =>
  {
    // act
    sut.Read<int>("my integer store").ToList();
  });
}

And this is the same example but using the SutBuilder to write (mostly) lambda-less code.

[Fact]
public void Read_GivenCompatibleDataStore_ReturnsAll()
{
  // arrange
  var sutBuilder = new SutBuilder();

  SetHappyPath(sutBuilder.InputBuilder);

  var sut = sutBuilder.CreateSut<DataRepository>();

  // act
  var result = sut.Read<int>("my integer store");

  // assert
  Assert.Collection(result, item1 => Assert.Equal(10, item1));
}

[Fact]
public void Read_GivenIncompatibleDataStore_Throws()
{
  // arrange
  var sutBuilder = new SutBuilder();

  SetHappyPath(sutBuilder.InputBuilder);

  // Breaking the happy path!
  // Get the list of data entities and modify it such that
  // a format exception will be thrown.
  var dataEntities = sutBuilder.GetRequiredService<List<DataEntity>>();
  dataEntities[0].Value = "this is not an integer";

  var sut = sutBuilder.CreateSut<DataRepository>();

  // assert
  Assert.Throws<FormatException>(() =>
  {
    // act
    sut.Read<int>("my integer store").ToList();
  });
}

This is the common "SetHappyPath" method referenced above.

protected static void SetHappyPath(InputBuilder arrange)
{
  var dataEntities = arrange.Instance<List<DataEntity>>();
  dataEntities.Add(new() { Id = Guid.NewGuid(), Value = 10, });

  var dataStore = arrange.Instance<IDataStore>();
  dataStore
    .LoadAll()
    .Returns(_ => dataEntities.Select(entity => entity.Clone()));

  arrange
    .Instance<IDataStoreFactory>()
    .GetOrCreateStore(default, default)
    .ReturnsForAnyArgs(dataStore);
}

See the SutFactory.Example project for more elaborate examples of using the SUT Factory for advanced cases as well as creating test spies, fakes etc.

The SUT factory uses 4 strategies when creating instances:

  • Using an instance provided by an external service provider.
  • Using a constructor.
  • A partial instance created by NSubstitute.
  • A substitute instance created by NSubstitute.

The automatic selection of a strategy should be sufficient for most tests. However, the SutBuilder does allow manual selection of a specific strategy via the "Advanced" input builder. If greater customization is needed, it is possible to replace a strategy by providing a custom implementation through an external service provider when creating the SutBuilder instance. The strategy selection will always prefer creating configurable substitutes unless an explicit interface/implementation pair is registered. In this case, the implementation will be used where the interface is injected into constructors.

Any instance can be retrieved for configuration and inspection. Both the SutBuilder and InputBuilder implement the IServiceProvider interface, allowing the use of the usual GetService and GetRequiredService methods.

A note on avoiding flaky tests. Ensure no Singleton instances is shared by multiple tests when using an external service provider. All instances created by the SUT factory is stored in the SutBuilder instance and is therefore local to the test. Sharing a SutBuilder instance in e.g. SetUp methods is discouraged.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0-beta1 138 1/1/2024