Skip to content

Commit a587621

Browse files
Merge branch 'develop' of github.com:TestStack/TestStack.BDDfy into testing/additional-tests
# Conflicts: # src/TestStack.BDDfy.Tests/Scanner/ReflectiveScanner/WhenStepMethodNameHasDoubleUnderscores.cs
2 parents 23e478f + bc48b4b commit a587621

178 files changed

Lines changed: 2817 additions & 763 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/async-support.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Async Support
2+
3+
BDDfy supports asynchronous step methods using both `Task`-returning methods and `async void` methods.
4+
5+
## Task-Returning Steps (Recommended)
6+
7+
The preferred approach is to return `Task` from your step methods:
8+
9+
```csharp
10+
public class AsyncScenario
11+
{
12+
private HttpClient _client = new();
13+
private HttpResponseMessage _response = null!;
14+
15+
async Task GivenTheServiceIsRunning()
16+
{
17+
await _client.GetAsync("http://localhost/health");
18+
}
19+
20+
async Task WhenIRequestTheResource()
21+
{
22+
_response = await _client.GetAsync("http://localhost/api/items");
23+
}
24+
25+
async Task ThenTheResponseIsSuccessful()
26+
{
27+
_response.EnsureSuccessStatusCode();
28+
}
29+
30+
[Fact]
31+
public void Execute()
32+
{
33+
this.BDDfy();
34+
}
35+
}
36+
```
37+
38+
### With the Fluent API
39+
40+
```csharp
41+
[Fact]
42+
public void Execute()
43+
{
44+
this.Given(s => s.GivenTheServiceIsRunning())
45+
.When(s => s.WhenIRequestTheResource())
46+
.Then(s => s.ThenTheResponseIsSuccessful())
47+
.BDDfy();
48+
}
49+
```
50+
51+
The fluent API has dedicated overloads accepting `Expression<Func<TScenario, Task>>`.
52+
53+
## Async Void Steps
54+
55+
BDDfy can also handle `async void` methods. It installs a custom `SynchronizationContext` to detect when the method completes:
56+
57+
```csharp
58+
public class AsyncVoidSteps
59+
{
60+
private object _sut = null!;
61+
62+
async void GivenSomeAsyncSetup()
63+
{
64+
_sut = await CreateSut();
65+
}
66+
67+
void ThenBddfyHasWaitedForSetupToComplete()
68+
{
69+
Assert.NotNull(_sut);
70+
}
71+
72+
private static async Task<object> CreateSut()
73+
{
74+
await Task.Delay(500);
75+
return new object();
76+
}
77+
78+
[Fact]
79+
public void Execute()
80+
{
81+
this.BDDfy();
82+
}
83+
}
84+
```
85+
86+
### Exception Handling in Async Void
87+
88+
Exceptions thrown in `async void` methods are captured and reported normally:
89+
90+
```csharp
91+
async void WhenSomethingFails()
92+
{
93+
await Task.Yield();
94+
throw new InvalidOperationException("Something went wrong");
95+
}
96+
```
97+
98+
BDDfy will catch this exception and mark the step as failed.
99+
100+
### Disabling Async Void Support
101+
102+
If async void detection causes issues in your environment:
103+
104+
```csharp
105+
Configurator.AsyncVoidSupportEnabled = false;
106+
```
107+
108+
## Async Delegate Steps (Fluent API)
109+
110+
Use `Func<Task>` with an explicit title:
111+
112+
```csharp
113+
this.Given(async () => await SetupDatabase(), "Given the database is seeded")
114+
.When(async () => await CallApi(), "When the API is called")
115+
.Then(async () => await VerifyResult(), "Then the result is correct")
116+
.BDDfy();
117+
```
118+
119+
## Best Practices
120+
121+
1. **Prefer `Task`-returning methods** over `async void` — they're easier to debug and don't require the special synchronization context
122+
2. **Avoid `Task.Result` or `.Wait()`** in step methods — use `async`/`await` throughout to prevent deadlocks
123+
3. **Use `async void` only** when retrofitting existing code or when the testing framework requires it

docs/configuration.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Configuration
2+
3+
BDDfy's behavior is controlled through the static `Configurator` class. Everything from step scanning to reporting is pluggable.
4+
5+
## The Configurator Class
6+
7+
```csharp
8+
using TestStack.BDDfy.Configuration;
9+
10+
// All configuration is done via static properties:
11+
Configurator.Processors // Per-scenario pipeline
12+
Configurator.BatchProcessors // End-of-run reporters
13+
Configurator.Scanners // Step discovery
14+
Configurator.IdGenerator // Unique key generation
15+
Configurator.StepExecutor // Step execution strategy
16+
Configurator.Humanizer // Method name → readable text
17+
Configurator.FluentScannerFactory // Fluent scanner creation
18+
Configurator.StepTitleFactory // Step title generation
19+
Configurator.CultureInfo // Culture for formatting
20+
Configurator.AsyncVoidSupportEnabled // async void step support (default: true)
21+
```
22+
23+
## Per-Scenario Processors
24+
25+
Processors run for each scenario in order of their `ProcessType`:
26+
27+
```csharp
28+
// Disable console output
29+
Configurator.Processors.ConsoleReport.Disable();
30+
31+
// Add a custom processor
32+
Configurator.Processors.Add(() => new MyCustomProcessor());
33+
```
34+
35+
Built-in processors (in execution order):
36+
1. **TestRunner** — executes the scenario steps
37+
2. **ConsoleReporter** — prints to console
38+
3. **ExceptionProcessor** — handles/rethrows exceptions
39+
4. **StoryCache** — stores results for batch reporters
40+
5. **Disposer** — disposes the test object if `IDisposable`
41+
42+
### Enabling/Disabling
43+
44+
```csharp
45+
Configurator.Processors.TestRunner.Disable();
46+
Configurator.Processors.ConsoleReport.Disable();
47+
Configurator.Processors.StoryCache.Disable();
48+
```
49+
50+
## Batch Processors
51+
52+
Run once after all scenarios complete (typically reporters):
53+
54+
```csharp
55+
Configurator.BatchProcessors.HtmlReport.Enable();
56+
Configurator.BatchProcessors.HtmlMetroReport.Disable();
57+
Configurator.BatchProcessors.MarkDownReport.Enable();
58+
Configurator.BatchProcessors.DiagnosticsReport.Enable();
59+
60+
// Add custom batch processor
61+
Configurator.BatchProcessors.Add(new MyBatchProcessor());
62+
```
63+
64+
## Step Scanners
65+
66+
Control how BDDfy discovers steps in your test classes:
67+
68+
```csharp
69+
// Disable attribute-based scanning
70+
Configurator.Scanners.ExecutableAttributeScanner.Disable();
71+
72+
// Disable method-name scanning
73+
Configurator.Scanners.DefaultMethodNameStepScanner.Disable();
74+
75+
// Add a custom step scanner
76+
Configurator.Scanners.Add(() => new MyCustomStepScanner());
77+
```
78+
79+
## Story Metadata Scanner
80+
81+
Replace how story metadata is discovered:
82+
83+
```csharp
84+
Configurator.Scanners.StoryMetadataScanner = () => new MyCustomMetadataScanner();
85+
```
86+
87+
## Humanizer
88+
89+
The humanizer converts method names into readable titles:
90+
91+
```csharp
92+
// Replace with a custom humanizer
93+
Configurator.Humanizer = new MyHumanizer();
94+
95+
public class MyHumanizer : IHumanizer
96+
{
97+
public string Humanize(string name)
98+
{
99+
// Custom logic to convert PascalCase/underscored names to text
100+
return name.Replace("_", " ");
101+
}
102+
}
103+
```
104+
105+
## Step Executor
106+
107+
Override how individual steps are executed:
108+
109+
```csharp
110+
Configurator.StepExecutor = new MyStepExecutor();
111+
112+
public class MyStepExecutor : IStepExecutor
113+
{
114+
public void Execute(Step step, object testObject)
115+
{
116+
// Add logging, timing, retry logic, etc.
117+
Console.WriteLine($"Executing: {step.Title}");
118+
step.Execute(testObject);
119+
}
120+
}
121+
```
122+
123+
## Step Title Factory
124+
125+
Customize how step titles are generated from expressions:
126+
127+
```csharp
128+
Configurator.StepTitleFactory = new MyStepTitleFactory();
129+
```
130+
131+
### IncludeInputsInStepTitle
132+
133+
Controls whether step arguments are appended to step titles (default: `true`):
134+
135+
```csharp
136+
Configurator.StepTitleFactory.IncludeInputsInStepTitle = false;
137+
```
138+
139+
### AddGherkinPrefixToSecondarySteps
140+
141+
When a step has an explicit title (via `[Given("...")]`, `[When("...")]`, `[Then("...")]`, or `[StepTitle("...")]`), BDDfy prepends the Gherkin keyword to the title by default. For consecutive steps in the same group, this means they get an "And" prefix. Set this to `false` to disable the "And" prefix on consecutive custom-titled steps:
142+
143+
```csharp
144+
Configurator.StepTitleFactory.AddGherkinPrefixToSecondarySteps = true; // default
145+
```
146+
147+
**With prefix enabled (default):**
148+
149+
```
150+
Scenario: With prefix enabled
151+
Given the user is logged in
152+
And the cart has items
153+
And the payment gateway is available
154+
When the user checks out
155+
Then the order is confirmed
156+
And a confirmation email is sent
157+
```
158+
159+
```csharp
160+
Configurator.StepTitleFactory.AddGherkinPrefixToSecondarySteps = false;
161+
```
162+
163+
**With prefix disabled:**
164+
165+
```
166+
Scenario: With prefix disabled
167+
Given the user is logged in
168+
the cart has items
169+
the payment gateway is available
170+
When the user checks out
171+
Then the order is confirmed
172+
a confirmation email is sent
173+
```
174+
175+
> **Note:** Primary step keywords (Given/When/Then) are always applied regardless of this setting. Only the consecutive "And" prefix on custom-titled steps is affected.
176+
177+
## Culture
178+
179+
Set the culture used for formatting values in reports:
180+
181+
```csharp
182+
Configurator.CultureInfo = new CultureInfo("en-US");
183+
```
184+
185+
## Async Void Support
186+
187+
BDDfy can detect and await `async void` step methods. Disable if it causes issues:
188+
189+
```csharp
190+
Configurator.AsyncVoidSupportEnabled = false;
191+
```
192+
193+
## Configuration Timing
194+
195+
Configure BDDfy before any tests run. With xUnit, use a module initializer or assembly fixture:
196+
197+
```csharp
198+
using System.Runtime.CompilerServices;
199+
using TestStack.BDDfy.Configuration;
200+
201+
public static class BDDfySetup
202+
{
203+
[ModuleInitializer]
204+
public static void Initialize()
205+
{
206+
Configurator.BatchProcessors.HtmlReport.Disable();
207+
Configurator.BatchProcessors.MarkDownReport.Enable();
208+
}
209+
}
210+
```
211+
212+
With NUnit, use `[SetUpFixture]`:
213+
214+
```csharp
215+
[SetUpFixture]
216+
public class BDDfySetup
217+
{
218+
[OneTimeSetUp]
219+
public void Setup()
220+
{
221+
Configurator.BatchProcessors.HtmlMetroReport.Enable();
222+
}
223+
}
224+
```

0 commit comments

Comments
 (0)