-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
457 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SutForge", "src\SutForge\SutForge.csproj", "{89FD47F0-EC29-4DDE-AF20-239C5E6DA174}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SutForge.Tests", "test\SutForge.Tests\SutForge.Tests.csproj", "{1F6C7AA4-AE6F-42E7-8DBA-E348F6EA8E5A}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{89FD47F0-EC29-4DDE-AF20-239C5E6DA174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{89FD47F0-EC29-4DDE-AF20-239C5E6DA174}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{89FD47F0-EC29-4DDE-AF20-239C5E6DA174}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{89FD47F0-EC29-4DDE-AF20-239C5E6DA174}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{1F6C7AA4-AE6F-42E7-8DBA-E348F6EA8E5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{1F6C7AA4-AE6F-42E7-8DBA-E348F6EA8E5A}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{1F6C7AA4-AE6F-42E7-8DBA-E348F6EA8E5A}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{1F6C7AA4-AE6F-42E7-8DBA-E348F6EA8E5A}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# SUT Forge | ||
|
||
## Introduction | ||
|
||
Experience unit testing and Test-Driven Development (TDD) in a whole new light with `SUTForge`, a simple yet powerful .NET package meticulously designed to streamline the creation of System Under Test (SUT) instances. `SUTForge` follows the Chicago School to unit testing (also referred to as the Classical School of unit testing), offering a clean and efficient alternative to the over-mocking issues associated with the London approach. What sets `SUTForge` apart is its high degree of extensibility, allowing developers to craft personalized extension methods and tailor SUTs to their specific testing needs. | ||
|
||
|
||
|
||
**Key Features**: | ||
|
||
- **Effortless TDD Integration**: `SUTForge` seamlessly integrates with Test-Driven Development (TDD), enabling developers to write tests first and then effortlessly construct and configure their SUTs using the intuitive builder pattern provided by `SUTForge`. | ||
|
||
- **Classical Approach to Unit Testing**: Embrace the classical approach to unit testing, emphasizing simplicity and readability. `SUTForge` encourages clean and maintainable tests without succumbing to over-mocking pitfalls, ensuring your unit tests remain valuable assets throughout the development lifecycle. | ||
|
||
- **Intuitive Builder Pattern**: Constructing SUTs becomes a breeze with `SUTForge`'s intuitive builder pattern. Say goodbye to the complexities of manual setup and embrace a syntax that makes unit test code concise, expressive, and easy to understand. | ||
|
||
- **Highly Extensible**: `SUTForge` goes beyond the basics, offering developers a high degree of extensibility. Write your own extension methods to customize SUTs according to your unique testing requirements. Tailor `SUTForge` to fit seamlessly into your specific development workflow. | ||
|
||
💪 Elevate your unit testing and TDD experience with `SUTForge`—a library that not only embraces the classical approach but also empowers you to extend and customize your testing capabilities. Your journey to efficient and maintainable tests starts here! | ||
|
||
## Getting Started | ||
|
||
To get started with `SUTForge`, you need to install the package in your .NET project. Once installed, you can start writing your unit tests using the `SUTForge`'s intuitive builder pattern. | ||
|
||
### Installation | ||
|
||
Install `SUTForge` via NuGet package manager: | ||
|
||
```shell | ||
Install-Package SUTForge | ||
``` | ||
|
||
### Writing Your First Test | ||
|
||
Here's an example of how you can write a unit test using `SUTForge`. This test checks whether a service is correctly built by the builder. | ||
|
||
```csharp | ||
[Test] | ||
public async Task Services_are_built_by_the_builder() | ||
{ | ||
// Arrange | ||
// Act | ||
var sut = await SutBuilder.Create | ||
.ConfigureServices(services => { services.AddSingleton<ISomeInterface, SomeImplementation>(); }) | ||
.BuildAsync(); | ||
|
||
// Assert | ||
sut.Should().NotBeNull(); | ||
sut.GetService<ISomeInterface>().Should().NotBeNull().And.BeOfType<SomeImplementation>(); | ||
} | ||
``` | ||
|
||
In the above test, we're using the `SutBuilder.Create` method to create a new instance of the SUT builder. We then configure the services by adding a singleton service of type `ISomeInterface` with an implementation of `SomeImplementation`. After building the SUT, we assert that the SUT is not null and that the service of type `ISomeInterface` is not null and is of type `SomeImplementation`. | ||
|
||
### Resolving Configuration for Services | ||
|
||
`SUTForge` also allows you to resolve configurations for services. Here's an example: | ||
|
||
```csharp | ||
[Test] | ||
public async Task Configuration_is_resolved_for_services() | ||
{ | ||
// Arrange | ||
// Act | ||
var sut = await SutBuilder.Create | ||
.ConfigureServices(services => { services.AddSingleton<ClassWithConfiguration>(); }) | ||
.BuildAsync(); | ||
|
||
// Assert | ||
sut.GetService<ClassWithConfiguration>().Should().NotBeNull(); | ||
} | ||
``` | ||
|
||
In this test, we're adding a service of type `ClassWithConfiguration` that requires configuration. After building the SUT, we assert that the service of type `ClassWithConfiguration` is not null, implying that the configuration was resolved correctly. | ||
|
||
### Customizing Configuration | ||
|
||
`SUTForge` also allows you to customize the configuration. Here's an example: | ||
|
||
```csharp | ||
[Test] | ||
public async Task Configuration_can_be_customized() | ||
{ | ||
// Arrange | ||
// Act | ||
var sut = await SutBuilder.Create | ||
.ConfigureServices(services => { services.AddSingleton<ClassWithConfiguration>(); }) | ||
.OnSetupConfiguration(builder => builder.AddInMemoryCollection(new Dictionary<string, string> | ||
{ | ||
{"Country", "France"} | ||
})) | ||
.BuildAsync(); | ||
|
||
// Assert | ||
var service = sut.GetService<ClassWithConfiguration>(); | ||
service.Should().NotBeNull(); | ||
service.Configuration["Country"].Should().Be("France"); | ||
} | ||
``` | ||
|
||
In this test, we're customizing the configuration by adding an in-memory collection with a key-value pair of "Country" and "France". After building the SUT, we assert that the service of type `ClassWithConfiguration` is not null and that the configuration value for "Country" is "France". | ||
|
||
### Extending SUTForge | ||
|
||
`SUTForge` is highly extensible, allowing you to write your own extension methods to customize SUTs according to your unique testing requirements. Here's an example: | ||
|
||
```csharp | ||
|
||
public static class SutBuilderExtensions | ||
{ | ||
public static SutBuilder WithCurrentTime(this SutBuilder builder, DateTime time) | ||
{ | ||
return builder.ConfigureServices(services => { services.AddSingleton<ITimeProvider(new DeterministicTimeProvider(time)); }); | ||
} | ||
} | ||
``` | ||
|
||
|
||
In this example, we're writing an extension method that allows us to customize the SUT by adding a singleton service of type `ITimeProvider` with an implementation of `DeterministicTimeProvider`. This extension method can then be used in our tests as follows: | ||
|
||
```csharp | ||
[Test] | ||
public async Task SUT_can_be_customized() | ||
{ | ||
// Arrange | ||
var time = 14.January(2021).At(12, 0, 0).AsUtc(); // Using FluentAssertions | ||
var sut = await SutBuilder.Create | ||
.AddApplicationServices() // Add the application services | ||
.WithCurrentTime(time) | ||
.BuildAsync(); | ||
|
||
var service = sut.GetRequiredService<ISomeFancyService>(); | ||
|
||
// Act | ||
var result = service.DoSomething(); | ||
|
||
// Assert | ||
// Assert something | ||
} | ||
``` | ||
|
||
With `SUTForge`, writing unit tests becomes a breeze. Embrace the classical approach to unit testing and elevate your TDD experience with `SUTForge`. Happy testing! | ||
|
||
## References | ||
|
||
- [Unit Testing Principles, Practices, and Patterns](https://www.goodreads.com/en/book/show/48927138) (Vladimir Khorikov) | ||
- [🚀 TDD, Where Did It All Go Wrong (Ian Cooper)](https://www.youtube.com/watch?v=EZ05e7EMOLM) | ||
- Uncle Bob (Robert C. Martin), TDD Harms Architecture https://blog.cleancoder.com/uncle-bob/2017/03/03/TDD-Harms-Architecture.html (On a shift from London to Chicago School of TDD; About fallacy of <class>Test file pattern) | ||
- [TDD Revisited - Ian Cooper - NDC London 2021](https://www.youtube.com/watch?v=vOO3hulIcsY) (on testing “ports&adapters” architecture with Chicago School of TDD) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
namespace SutForge; | ||
|
||
/// <summary> | ||
/// Represents a System Under Test. This SUT implements <see cref="IServiceProvider"/> to allow to retrieve services from the <see cref="IServiceProvider"/> used to build the SUT. | ||
/// </summary> | ||
public class Sut : IServiceProvider | ||
{ | ||
private IServiceProvider _serviceProviderImplementation; | ||
|
||
internal Sut(IServiceProvider serviceProviderImplementation) | ||
{ | ||
_serviceProviderImplementation = serviceProviderImplementation; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public object? GetService(Type serviceType) | ||
{ | ||
return _serviceProviderImplementation.GetService(serviceType); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
namespace SutForge; | ||
|
||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
/// <summary> | ||
/// Allows to build a <see cref="Sut"/> (System Under Test) with a custom configuration. | ||
/// </summary> | ||
public class SutBuilder | ||
{ | ||
private Action<IServiceCollection> configureServices = _ => { }; | ||
private Action<ConfigurationBuilder> configurationBuilders = _ => { }; | ||
private Func<Sut, Task> onCreated = _ => Task.CompletedTask; | ||
|
||
protected SutBuilder() | ||
{ | ||
} | ||
|
||
public static SutBuilder Create => new(); | ||
|
||
/// <summary> | ||
/// Builds the <see cref="Sut"/> (System Under Test) with the provided configuration. | ||
/// </summary> | ||
/// <returns>An instance of <see cref="Sut"/> ready to be tested</returns> | ||
public async Task<Sut> BuildAsync() | ||
{ | ||
// Create the empty service collection and the configuration | ||
var services = new ServiceCollection(); | ||
var configurationBuilder = new ConfigurationBuilder(); | ||
|
||
// Execute the registered configuration builders | ||
this.configurationBuilders(configurationBuilder); | ||
|
||
// Add the configuration to the service collection | ||
services.AddSingleton<IConfiguration>(configurationBuilder.Build()); | ||
|
||
// Execute the registered services configuration | ||
this.configureServices(services); | ||
|
||
// Build the service provider | ||
var serviceProvider = services.BuildServiceProvider(); | ||
|
||
// Create the SUT and execute the registered operations | ||
var sut = new Sut(serviceProvider); | ||
await this.onCreated(sut); | ||
|
||
return sut; | ||
} | ||
|
||
/// <summary> | ||
/// Registers an action to configure the services of the <see cref="Sut"/> (System Under Test). | ||
/// </summary> | ||
public SutBuilder ConfigureServices(Action<IServiceCollection> configureServices) | ||
{ | ||
this.configureServices += configureServices; | ||
|
||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Registers an action to configure a <see cref="IConfiguration"/> which will be passed to the <see cref="OnCreatedAsync"/> methods. | ||
/// </summary> | ||
public SutBuilder OnSetupConfiguration(Action<ConfigurationBuilder> builder) | ||
{ | ||
this.configurationBuilders += builder; | ||
|
||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Registers an action to execute when the <see cref="Sut"/> (System Under Test) is created. | ||
/// </summary> | ||
public SutBuilder OnCreatedAsync(Func<Sut, Task> operation) | ||
{ | ||
this.onCreated += operation; | ||
|
||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<RootNamespace>Sutb</RootNamespace> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
global using NUnit.Framework; | ||
global using FluentAssertions; |
Oops, something went wrong.