{"id":32351500,"url":"https://github.com/dovic95/sutforge","last_synced_at":"2025-10-24T09:43:24.407Z","repository":{"id":214068173,"uuid":"735613840","full_name":"dovic95/SUTForge","owner":"dovic95","description":"Simplifying ASP.NET Core unit testing and Test-Driven Development with an intuitive builder pattern, classical/Detroit approach, and high extensibility for effortless creation and customization of System Under Test instances.","archived":false,"fork":false,"pushed_at":"2024-01-06T11:33:38.000Z","size":44,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-21T15:22:22.329Z","etag":null,"topics":["aspnetcore","classical","csharp","detroit","dotnet","dotnet-core","dotnet-standard","tdd","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dovic95.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-12-25T14:50:01.000Z","updated_at":"2024-01-09T04:33:23.000Z","dependencies_parsed_at":"2024-01-06T11:34:36.137Z","dependency_job_id":"6835ba63-75fc-4a14-87e2-a101be8b0542","html_url":"https://github.com/dovic95/SUTForge","commit_stats":null,"previous_names":["dovic95/sutforge"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/dovic95/SUTForge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dovic95%2FSUTForge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dovic95%2FSUTForge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dovic95%2FSUTForge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dovic95%2FSUTForge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dovic95","download_url":"https://codeload.github.com/dovic95/SUTForge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dovic95%2FSUTForge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280776503,"owners_count":26388949,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-24T02:00:06.418Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspnetcore","classical","csharp","detroit","dotnet","dotnet-core","dotnet-standard","tdd","unit-testing"],"created_at":"2025-10-24T09:43:21.376Z","updated_at":"2025-10-24T09:43:24.392Z","avatar_url":"https://github.com/dovic95.png","language":"C#","readme":"# SUT Forge\n\n[![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/SUTForge)](https://www.nuget.org/packages/SutForge)\n\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"favicon.png\" alt=\"Icon\" /\u003e\n\u003c/p\u003e\n\nSimplifying ASP.NET Core unit testing and Test-Driven Development with an intuitive builder pattern, [classical/Detroit](https://zone84.tech/architecture/london-and-detroit-schools-of-unit-tests/) approach, and high extensibility for effortless creation and customization of System Under Test instances.\n\n## Introduction\n\n**SUTForge: Empowering Unit Testing and TDD with Seamless IServiceCollection Extension**\n\nRevolutionize your approach to unit testing and Test-Driven Development (TDD) with `SUTForge`, a robust .NET package meticulously crafted to simplify the creation of System Under Test (SUT) instances.\n\nFollowing the [Detroit School of Unit Testing](https://zone84.tech/architecture/london-and-detroit-schools-of-unit-tests/) (Classical or Chicago School of unit testing), `SUTForge` provides a refreshing alternative to the over-mocking challenges associated with the London approach.\n\nOne standout feature of `SUTForge` is its extensive extensibility. Developers can seamlessly integrate their existing extension methods on `IServiceCollection` within the `ConfigureServices` method in the `Startup` class. This capability ensures a unified composition root for both production code and unit tests.\n\n**Key Features:**\n\n- **Effortless TDD Integration:** Seamlessly integrate `SUTForge` into your Test-Driven Development (TDD) workflow. Write tests first and effortlessly construct and configure SUTs using the intuitive builder pattern provided by `SUTForge`.\n\n- **Detroit School of Unit Testing:** Embrace the simplicity and readability of the [Detroit approach](https://zone84.tech/architecture/london-and-detroit-schools-of-unit-tests/). `SUTForge` facilitates clean and maintainable tests without falling into over-mocking pitfalls, ensuring the enduring value of your unit tests throughout the development lifecycle.\n\n- **Intuitive Builder Pattern:** Say goodbye to the complexities of manual setup. `SUTForge` simplifies SUT construction with an intuitive builder pattern, offering a syntax that makes unit test code concise, expressive, and easy to understand.\n\n- **Highly Extensible:** Beyond the basics, `SUTForge` excels in extensibility. Craft your extension methods to customize SUTs, aligning them with your unique testing requirements. Tailor `SUTForge` to seamlessly integrate into your specific development workflow.\n\n💪 Elevate your unit testing and TDD experience with `SUTForge`—a library that not only champions the Detroit approach but also empowers you to effortlessly plug your existing extension methods onto `IServiceCollection`, ensuring a harmonized composition root for both production and test environments. Your journey to efficient and maintainable tests begins here!\n\n## Getting Started\n\nTo get started with `SUTForge`, you need to install the package in your ASP.NET project. Once installed, you can start writing your unit tests using the `SUTForge`'s intuitive builder pattern.\n\n### Installation\n\nInstall `SUTForge` via .NET CLI:\n\n- 📦 [NuGet](https://nuget.org/packages/SUTForge): `dotnet add package SUTForge`\n\n### Writing Your First Test\n\nHere'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.\n\n```csharp\n[Test]\npublic async Task Services_are_built_by_the_builder()\n{\n    // Arrange\n    // Act\n    var sut = await SutBuilder.Create\n        .ConfigureServices(services =\u003e { services.AddSingleton\u003cISomeInterface, SomeImplementation\u003e(); })\n        .BuildAsync();\n\n    // Assert\n    sut.Should().NotBeNull();\n    sut.GetService\u003cISomeInterface\u003e().Should().NotBeNull().And.BeOfType\u003cSomeImplementation\u003e();\n}\n```\n\nIn 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`.\n\n### Resolving Configuration for Services\n\n`SUTForge` also allows you to resolve configurations for services. Here's an example:\n\nConsidering the following class that make use of `IConfiguration`:\n\n```csharp\nclass ClassWithConfiguration\n{   \n    private readonly IConfiguration _configuration;\n\n    public ClassWithConfiguration(IConfiguration configuration)\n    {\n        _configuration = configuration;\n    }\n\n    // ...\n}\n```\n\nIn this test, we're adding a service of type `ClassWithConfiguration` that requires configuration:\n\n```csharp\n[Test]\npublic async Task Configuration_is_resolved_for_services()\n{\n    // Arrange\n    // Act\n    var sut = await SutBuilder.Create\n        .ConfigureServices(services =\u003e { services.AddSingleton\u003cClassWithConfiguration\u003e(); })\n        .BuildAsync();\n\n    // Assert\n    sut.GetService\u003cClassWithConfiguration\u003e().Should().NotBeNull();\n}\n```\n\n After building the SUT, we assert that the service of type `ClassWithConfiguration` is not null, implying that the configuration was resolved correctly.\n\n### Customizing Configuration\n\n`SUTForge` also allows you to customize the configuration. Here's an example:\n\n```csharp\n[Test]\npublic async Task Configuration_can_be_customized()\n{\n    // Arrange\n    // Act\n    var sut = await SutBuilder.Create\n        .ConfigureServices(services =\u003e { services.AddSingleton\u003cClassWithConfiguration\u003e(); })\n        .OnSetupConfiguration(builder =\u003e builder.AddInMemoryCollection(new Dictionary\u003cstring, string\u003e\n        {\n            {\"Country\", \"France\"}\n        }))\n        .BuildAsync();\n\n    // Assert\n    var service = sut.GetService\u003cClassWithConfiguration\u003e();\n    service.Should().NotBeNull();\n    service.Configuration[\"Country\"].Should().Be(\"France\");\n}\n```\n\nIn 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\".\n\n### Extending SUTForge\n\n`SUTForge` is highly extensible, allowing you to use existing extension methods on `IServiceCollection` or to write your own extension methods to customize SUTs according to your unique testing requirements. Here's an example:\n\n```csharp   \n\npublic static class SutBuilderExtensions\n{\n    public static SutBuilder WithCurrentTime(this SutBuilder builder, DateTime time)\n    {\n        return builder.ConfigureServices(services =\u003e { services.AddSingleton\u003cITimeProvider(new DeterministicTimeProvider(time)); });\n    }\n}   \n```\n\nIn 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` to control the time of the SUT. This extension method can then be used in our tests as follows:\n\n```csharp   \n[Test]\npublic async Task SUT_can_be_customized()\n{\n    // Arrange\n    var time = 14.January(2021).At(12, 0, 0).AsUtc(); // Using FluentAssertions\n    \n    var sut = await SutBuilder.Create\n        .AddMyApplicationServices() // 💡 Add the application services (the same method you'll use in your Startup class)\n        .WithCurrentTime(time)\n        .BuildAsync();\n    \n    var service = sut.GetRequiredService\u003cISomeFancyService\u003e();\n    \n    // Act\n    var result = service.DoSomething();\n\n    // Assert\n    // Assert something\n}\n```\n\nWith `SUTForge`, writing unit tests becomes a breeze. Embrace the Detroit approach of unit testing and elevate your TDD experience with `SUTForge`. Happy testing!\n\n## References\n\n- [Unit Testing Principles, Practices, and Patterns](https://www.goodreads.com/en/book/show/48927138) (Vladimir Khorikov) \n- [🚀 TDD, Where Did It All Go Wrong (Ian Cooper)](https://www.youtube.com/watch?v=EZ05e7EMOLM)\n- [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 Detroit/Chicago/Classical School of TDD; About fallacy of \u003cclass\u003eTest file pattern)\n- [TDD Revisited - Ian Cooper - NDC London 2021](https://www.youtube.com/watch?v=vOO3hulIcsY) (on testing “ports\u0026adapters” architecture with Chicago School of TDD)\n- [Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html#ClassicalAndMockistTesting)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdovic95%2Fsutforge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdovic95%2Fsutforge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdovic95%2Fsutforge/lists"}