Intent.Integration.HttpClients.Stubs
This module generates stub implementations of the service contracts produced by the Intent.Integration.HttpClients module. It generates a dedicated <App>.Infrastructure.Stubs project containing one stub per HTTP client, plus a dependency-injection registration that swaps the real HTTP clients for stubs when enabled through configuration.
Stubs let you run and test an application without the downstream services its HTTP clients call — useful for local development, demos, and integration tests where the real endpoints are unavailable or undesirable.
What This Module Generates
On install the module adds a dedicated stub project beside the application's Infrastructure project, then generates the stubs and their registration into it:
<App>.Infrastructure.Stubsproject — created by an on-install migration. It is placed in the same solution folder as the<App>.Infrastructureproject and inherits its .NET settings (SDK, target framework, implicit usings), so the stubs compile against the same framework as the rest of the application.<Service>HttpClientStub(roleStubs.HttpClientStub) — one class per HTTP client service proxy. Each implements the generated service contract interface and provides a method per endpoint that returns a safe default value.StubHttpClientConfiguration(roleStubs.Configuration) — a static class exposing theAddStubHttpClients(this IServiceCollection, IConfiguration)extension method that conditionally replaces the real client registrations with stubs.
The Stub Project
The <App>.Infrastructure.Stubs project does not exist in a fresh application — it is created the first time this module is installed, by an on-install migration that extends the Codebase Structure designer:
- It locates the application's Infrastructure project (via its
Infrastructureoutput anchor, falling back to the*.Infrastructurenaming convention). - It creates an
<App>.Infrastructure.StubsC# project in the same solution folder, copying the Infrastructure project's.NET Settingsso the stub project targets the same framework. - It adds a single
Stubsoutput anchor inside the new project. The stub templates bind to this anchor by role, so their output lands inside the framework-targeted stub project.
The migration is idempotent: if an <App>.Infrastructure.Stubs project already exists it makes no changes, so re-running the Software Factory or re-installing the module is safe.
Stub Implementations
For each HTTP client, the module generates a stub class implementing the service contract. Every endpoint method returns a default value so the application can run without the downstream service, and the body is yours to customize:
public class CustomersServiceHttpClientStub : ICustomersService
{
[IntentManaged(Mode.Fully, Body = Mode.Ignore)]
public async Task<CustomerDto> GetCustomerByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await Task.FromResult(new CustomerDto
{
Id = Guid.Empty,
Email = string.Empty,
Name = string.Empty,
Surname = string.Empty
});
}
// ... a method per endpoint
}
Each method is marked [IntentManaged(Mode.Fully, Body = Mode.Ignore)]: the Software Factory writes the safe default body once, then leaves it untouched on subsequent runs while keeping the method signature in sync with the contract. Edit a stub's body to give it meaningful canned behaviour — your implementation is preserved across regenerations, with no attribute change needed.
Paged results
When an endpoint returns a PagedResult<T>, the stub returns a single-item page that reflects the request rather than an empty, all-zero result. TotalCount and PageCount are set to 1 (the one item returned), and PageNumber/PageSize echo the request's paging fields:
public async Task<PagedResult<OrderDto>> GetOrdersPagedAsync(
GetOrdersPagedQuery query,
CancellationToken cancellationToken = default)
{
return await Task.FromResult(new PagedResult<OrderDto>
{
TotalCount = 1,
PageCount = 1,
PageSize = query.PageSize,
PageNumber = query.PageNo,
Data = new List<OrderDto> { /* one fully-populated item */ }
});
}
The paging fields are located by name on the query/command, using the same conventions as the rest of Intent — page / pageno / pagenum / pagenumber / pageindex for the page number and size / pagesize for the page size. If a paged request has no recognizable paging fields, those two counters fall back to 0.
Conditional Registration
The generated StubHttpClientConfiguration exposes AddStubHttpClients, which replaces a real client registration with its stub only when the corresponding UseStub setting is enabled:
public static IServiceCollection AddStubHttpClients(this IServiceCollection services, IConfiguration configuration)
{
if (UseStubHttpClient(configuration, "CleanArchitecture.Comprehensive.Services", "CustomersService"))
{
services.RemoveAll<ICustomersService>();
services.AddTransient<ICustomersService, CustomersServiceHttpClientStub>();
}
// ... an entry per client
return services;
}
The module wires services.AddStubHttpClients(configuration); into the application's composition root at a priority that orders it after the real HTTP client registrations (AddInfrastructure), so the real registrations exist to be removed and replaced. When UseStub is false (the default) the real clients are left untouched.
Why a dedicated project
The stubs and their AddStubHttpClients registration are isolated in <App>.Infrastructure.Stubs rather than folded into the main infrastructure. This makes stubbing a single, removable capability with clear operational guarantees:
- Excludable from production. The entire mechanism — the stub classes and the registration that activates them — lives in one project that can be omitted from production build and deployment pipelines, so stub code never ships to environments where it does not belong.
- Safe by construction, not just by configuration. If the stubs project is not deployed,
AddStubHttpClientsis not there to run. Noappsettings.jsonedit or environment override can swap a real client for a stub, so in production the real endpoints simply cannot be switched off. - Contained control. Whether stubs are in play is governed entirely by this one project and its
UseStubsettings, keeping the decision explicit and in one place rather than threaded through the main registration code.
Module Settings
This module introduces no Module Builder settings. Instead it registers a per-client-group UseStub application setting (defaulting to false) into the generated appsettings.json, alongside the existing Intent.Integration.HttpClients settings:
"HttpClients": {
"CleanArchitecture.Comprehensive.Services": {
"Uri": "https://localhost:{app_port}/",
"IdentityClientKey": "default",
"Timeout": "00:01:00",
"UseStub": false
}
}
Set UseStub to true to swap that group's clients for their stubs. Resolution is checked most-specific first:
HttpClients:<ServiceName>:UseStub— overrides a single service.HttpClients:<GroupName>:UseStub— applies to every service in the client group.
If neither key is present the value defaults to false, so stubs are never used unless explicitly opted in.
Related Modules
Intent.Integration.HttpClients
Generates the real HTTP client implementations and their appsettings.json configuration. This module generates stand-in implementations of the same service contracts and reuses the HttpClients configuration section, adding the UseStub switch.
Intent.Application.Contracts.Clients
Generates the service contract interfaces and DTOs that the stubs implement and return.
Intent.VisualStudio.Projects
Provides the Codebase Structure project model that the on-install migration extends to add the <App>.Infrastructure.Stubs project.