Dependency Injection Integration
TinyBDD.Extensions.DependencyInjection provides seamless integration between TinyBDD and Microsoft.Extensions.DependencyInjection, enabling DI-aware scenario context creation and configuration.
Installation
dotnet add package TinyBDD.Extensions.DependencyInjection
Quick Start
// In Startup.cs or Program.cs
services.AddTinyBdd();
// In your service or controller
public class OrderService
{
private readonly IScenarioContextFactory _contextFactory;
public OrderService(IScenarioContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
public async Task<OrderResult> ProcessOrder(Order order)
{
var context = _contextFactory.Create("Order Processing", "Process single order");
await Bdd.Given(context, "order received", () => order)
.When("validated", o => ValidateOrder(o))
.When("saved to database", o => SaveOrder(o))
.Then("confirmation sent", o => SendConfirmation(o));
return new OrderResult { Success = context.Steps.All(s => s.Error == null) };
}
}
Service Registration
Basic Registration
services.AddTinyBdd();
This registers:
IScenarioContextFactoryas scopedITraitBridgeas singleton (defaultNullTraitBridge)TinyBddOptionswith defaults
With Configuration
services.AddTinyBdd(options =>
{
options.DefaultScenarioOptions = new ScenarioOptions
{
ContinueOnError = true,
HaltOnFailedAssertion = false,
StepTimeout = TimeSpan.FromSeconds(30),
MarkRemainingAsSkippedOnFailure = true
};
options.EnableStepTiming = true;
});
Custom Trait Bridge
For test framework integration or custom tag handling:
// Using a type
services.AddTinyBddTraitBridge<MyCustomTraitBridge>();
// Using an instance
services.AddTinyBddTraitBridge(new LoggingTraitBridge(logger));
API Reference
TinyBddOptions
| Property | Type | Default | Description |
|---|---|---|---|
DefaultScenarioOptions |
ScenarioOptions |
new() |
Default options applied to all created contexts |
RegisterContextFactory |
bool |
true |
Whether to register IScenarioContextFactory |
EnableStepTiming |
bool |
false |
Enable detailed step timing through DI pipeline |
IScenarioContextFactory
public interface IScenarioContextFactory
{
/// <summary>
/// Creates a new ScenarioContext with explicit names.
/// </summary>
ScenarioContext Create(
string featureName,
string scenarioName,
string? featureDescription = null);
/// <summary>
/// Creates a ScenarioContext from object type attributes.
/// </summary>
ScenarioContext CreateFromAttributes(
object featureSource,
string? scenarioName = null);
}
Usage Patterns
In ASP.NET Core Controllers
[ApiController]
[Route("api/[controller]")]
public class WorkflowController : ControllerBase
{
private readonly IScenarioContextFactory _factory;
private readonly ILogger<WorkflowController> _logger;
public WorkflowController(
IScenarioContextFactory factory,
ILogger<WorkflowController> logger)
{
_factory = factory;
_logger = logger;
}
[HttpPost("process")]
public async Task<IActionResult> ProcessRequest([FromBody] Request request)
{
var context = _factory.Create("API Processing", $"Process {request.Type}");
try
{
await Bdd.Given(context, "request received", () => request)
.When("validated", r => Validate(r))
.When("processed", r => Process(r))
.Then("completed", r => r.IsComplete);
// Log step results
foreach (var step in context.Steps)
{
_logger.LogInformation(
"{Kind} {Title}: {Status} ({Elapsed}ms)",
step.Kind, step.Title,
step.Error == null ? "OK" : "FAILED",
step.Elapsed.TotalMilliseconds);
}
return Ok(new { Steps = context.Steps.Count, Success = true });
}
catch (BddStepException ex)
{
_logger.LogError(ex, "Workflow failed at step: {Step}", ex.Message);
return BadRequest(new { Error = ex.Message });
}
}
}
In Background Services
public class DataSyncService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public DataSyncService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _scopeFactory.CreateScope();
var factory = scope.ServiceProvider.GetRequiredService<IScenarioContextFactory>();
var context = factory.Create("Data Sync", "Synchronize external data");
await Bdd.Given(context, "fetch pending items", () => GetPendingItems())
.When("transform data", items => TransformItems(items))
.When("save to database", data => SaveData(data))
.Then("all items synced", result => result.FailedCount == 0);
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
With Scoped Dependencies
public class OrderWorkflow
{
private readonly IScenarioContextFactory _factory;
private readonly IOrderRepository _repository;
private readonly IPaymentService _payments;
private readonly INotificationService _notifications;
public OrderWorkflow(
IScenarioContextFactory factory,
IOrderRepository repository,
IPaymentService payments,
INotificationService notifications)
{
_factory = factory;
_repository = repository;
_payments = payments;
_notifications = notifications;
}
public async Task<OrderResult> FulfillOrder(Guid orderId, CancellationToken ct)
{
var context = _factory.Create("Order Fulfillment", $"Fulfill order {orderId}");
await Bdd.Given(context, "order loaded", () => _repository.GetByIdAsync(orderId))
.When("payment processed", order => _payments.ProcessAsync(order, ct))
.And("inventory reserved", order => _repository.ReserveInventoryAsync(order, ct))
.When("shipment created", order => CreateShipmentAsync(order, ct))
.Then("customer notified", order => _notifications.SendOrderConfirmationAsync(order, ct));
return new OrderResult
{
OrderId = orderId,
Success = context.Steps.All(s => s.Error == null),
Steps = context.Steps.Select(s => new StepSummary(s.Kind, s.Title, s.Error?.Message)).ToList()
};
}
}
Testing with DI
The same workflow classes work in both production and test environments:
public class OrderWorkflowTests
{
[Fact]
public async Task FulfillOrder_WithValidOrder_CompletesSuccessfully()
{
// Arrange
var services = new ServiceCollection();
services.AddTinyBdd();
services.AddSingleton<IOrderRepository>(new FakeOrderRepository());
services.AddSingleton<IPaymentService>(new FakePaymentService());
services.AddSingleton<INotificationService>(new FakeNotificationService());
services.AddScoped<OrderWorkflow>();
var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
var workflow = scope.ServiceProvider.GetRequiredService<OrderWorkflow>();
// Act
var result = await workflow.FulfillOrder(Guid.NewGuid(), CancellationToken.None);
// Assert
Assert.True(result.Success);
Assert.Equal(4, result.Steps.Count);
}
}
Configuration from appsettings.json
// appsettings.json
{
"TinyBdd": {
"StepTimeoutSeconds": 30,
"ContinueOnError": false
}
}
// Program.cs
services.AddTinyBdd(options =>
{
var config = configuration.GetSection("TinyBdd");
options.DefaultScenarioOptions = new ScenarioOptions
{
StepTimeout = TimeSpan.FromSeconds(config.GetValue<int>("StepTimeoutSeconds", 30)),
ContinueOnError = config.GetValue<bool>("ContinueOnError", false)
};
});
Best Practices
- Use scoped contexts: Create contexts within request/operation scope, not as singletons
- Log step results: Capture
context.Stepsfor observability in production - Handle exceptions gracefully: Wrap workflows in try/catch for
BddStepException - Configure timeouts: Use
StepTimeoutto prevent runaway operations - Test with real DI: Use the same service registration in tests for confidence
Next Steps
- Hosting Integration - Run workflows as hosted services
- Reporting Extension - JSON reporting and observer pattern
- Orchestrator Patterns - Advanced workflow patterns
- Enterprise Samples - Production-ready examples
Return to: Extensions Index | User Guide