Take DependencyModules' [Mock], and offer NUnit, Moq and FakeItEasy in the templates - #284
Merged
Merged
Conversation
…n the templates Hardened.Shared.Testing's own MockAttribute was hardwired to NSubstitute and carried it as a dependency, a duplicate of the pluggable [Mock] DependencyModules ships with its per-library support packages. It goes, with the NSubstitute reference; a test project now names its library with [assembly: NSubstituteSupport], [MoqSupport] or [FakeItEasySupport] and the matching package. Every template takes --test-framework xunit|nunit and --mocks nsubstitute|moq|fakeiteasy, with xUnit and NSubstitute the defaults. The verification script runs each of the other three at least once and holds every generated test project to the runner and library it asked for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ipjohnson
added a commit
that referenced
this pull request
Sep 5, 2026
) A code-first handler's operationId was the method name in camelCase, so the Refit client from a code-first template said All() where the spec-first one said ListTodos(), and the template's Refit tests carried a codeFirst branch for the names. [Operation("listTodos")] on the handler declares the id; the document writes it as given, a derived name that collides with a declared one is prefixed with its tag as two derived names are, and two handlers declaring one id is HRDOA004. Spec-first handlers now carry their description's id verbatim rather than a camel-cased round trip, which is why the Smithy fixture's export moves to its shape names. The template's code-first sample declares the contract's four ids, and the same constraints: [Range(Min = 1)] on the id and [StringLength(1, 64)] on the title, so the code-first document declares the same 400s with the same RequestValidationError schema. The Refit method names, the constraint tests and the malformed-id tests therefore run in every contract mode with no branch, and DocumentStatusTests has one expectation set for every mode that declares its statuses. The socket test's Kiota and Refit blocks gain the NUnit assert pair #284 put around every other assert; merged after it, #283 had left them xUnit-only, so the script's kiota:nunit row did not compile. Co-authored-by: Ian Johnson <ianjohnson@mac.mynetworksettings.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
[Mock]is DependencyModules' attribute now.Hardened.Shared.Testingcarried its ownMockAttribute, hardwired toNSubstitute.Substitute.For, with NSubstitute as a package dependency. DependencyModules has had the pluggable form since the split into per-library packages: its[Mock]builds nothing itself and asks theIMockSupportAttributein scope, whichDependencyModules.NSubstitute,DependencyModules.MoqandDependencyModules.FakeItEasyprovide as[NSubstituteSupport],[MoqSupport]and[FakeItEasySupport]. That migration was owed when the split landed and did not happen in #92, #201, #273 or #281. This makes it.On top of it, every template takes two new options:
--test-framework xunit|nunitand--mocks nsubstitute|moq|fakeiteasy. The defaults are xUnit and NSubstitute, so a baredotnet newscaffolds what it did before.Breaking
[Mock]moved fromHardened.Shared.Testing.AttributestoDependencyModules.Testing.Attributes, and it needs a support attribute in scope. A test project on 0.20 adds one package reference (DependencyModules.NSubstituteat the DependencyModules line the framework is on) and one line beside its entry point:[assembly: NSubstituteSupport]. Without the attribute a[Mock]parameter fails with "Mock library not found" rather than handing back the real service.Hardened.Shared.Testingno longer brings NSubstitute. A project that used it transitively references it, or the DependencyModules package, itself.The repository's own consumers are moved: the two Web integration test projects,
Hardened.Shared.Testing.TestsandHardened.Shared.Testing.NUnit.Tests.MockAttributeTeststested the deleted class and goes with it;ParameterInjectionTestscovers[Mock]through the resolver under the new attribute. The approved public API forHardened.Shared.Testingloses the class.What each option changes in a generated project
--test-framework nunitswaps the runner package (Hardened.Shared.Testing.NUnitfor.xUnit), the framework packages (NUnitandNUnit3TestAdapterforxunit.v3andxunit.runner.visualstudio), drops the<OutputType>Exe</OutputType>xUnit v3 needs, changes the global using, and writes every assertion in NUnit's constraint form.[HardenedTest]reads the same on both, which is what the runner split was for.--mocksswaps one package reference and one assembly attribute inBootstrap.cs, and writes the sample that resolves a mock for that library:[Mock] TwithArg.Any/Returnsfor NSubstitute, aMock<T>parameter withSetupfor Moq,[Mock] TwithA.CallTofor FakeItEasy.The web template gains that sample:
TodoStoreMockTestsputs a mockITodoStorebehindGET /todos/{id}throughITestWebApp, so it is the same file whichever client was chosen. The library sample's[Mock]test is written the three ways. The function sample has no seam to mock, so there the option only swaps the package and the attribute.The generated
Directory.Packages.propspins the mock package at$(DependencyModulesVersion), stamped at pack from the property the framework builds against, the same wayHardenedVersionis. A literal would have gone stale the way the RazorBlade snippet did.stage-templates.pystamps both tokens and fails if either reached nothing.The README and AGENTS.md of each template follow the options: the runner paragraph, the mocking paragraph naming the library and the attribute, and the assertion lines in every sample.
Verification
scripts/verify-templates.shcombos carry two more fields,host:contract[:model[:client[:tests[:mocks]]]]. Three web rows leave the defaults, chosen so each of NUnit, Moq and FakeItEasy compiles and passes on a client variant that carries the fork:kestrel:code:response:kiota:nunit,kestrel:openapi:response:refit:xunit:moq,kestrel:code:throws:none:nunit:fakeiteasy. The library runs four ways and the function template two more. For every row,check_test_optionsholds the generated test project to the runner and the mock library it asked for and none of the others, on package references and the assembly attribute;run_testsrequires the run to report tests passed, sincedotnet testanswers 0 for a project that discovered none, and then runs the mock test by name so a support attribute that failed to reach the output cannot pass.check_generatedalso fails on either version token reaching the output unstamped.Locally: the solution builds under the CI flags, all 7261 tests pass, and the full
verify-templates.shrun is green: 16 web rows including both Smithy rows, 4 library rows, 5 function and Lambda rows, the dotted-name, host-independence and alias checks, and the named mock test passing in all 21 projects that carry it.Docs
The mocks guide says NSubstitute arrives with
Hardened.Shared.Testing, and the testing guide, the attributes reference and the templates page's option tables need the two options. That is a Hardened.Docs change, queued behind #32.🤖 Generated with Claude Code