Step Methods
Docs for xUnit SDK v0.4.0
The step methods — Given(), When(), Then(), And(), and But() — are
the building blocks of BDD scenarios in FeatureTest. Each records a step in the
formatted output and executes a lambda containing the test logic.
Given("a customer from 'Australia'", ctx =>
{
_country = ctx.Step!.Values[0].AsString();
});
When("the order totals '100.00'", ctx =>
{
_total = ctx.Step!.Values[0].AsDecimal();
});
Then("shipping is 'Free'", ctx =>
{
Assert.Equal(ctx.Step!.Values[0].AsString(), CalculateShipping(_country, _total));
});
Reference
Method Signatures
All five keywords support four callback shapes, each with a concise
title, step overload and a title, description, step overload:
// 1. Sync — no context
void Given(string title, Action step);
void Given(string title, string? description, Action step);
// 2. Sync — with context (for value extraction)
void Given(string title, Action<LiveDocContext> step);
void Given(string title, string? description, Action<LiveDocContext> step);
// 3. Async — no context
Task Given(string title, Func<Task> step);
Task Given(string title, string? description, Func<Task> step);
// 4. Async — with context
Task Given(string title, Func<LiveDocContext, Task> step);
Task Given(string title, string? description, Func<LiveDocContext, Task> step);
Replace Given with When, Then, And, or But — the signatures are identical.
Parameters
-
title:string— The step description. Appears in formatted output. Can contain:- Quoted values:
'value'— extracted viactx.Step.Values - Named parameters:
<name:value>— extracted viactx.Step.Params - Outline placeholders:
<paramName>— replaced with example data in output
- Quoted values:
-
description:string?— Optional Markdown rendered beneath the title. In the three-argument overload it comes immediately aftertitle, so the specification reads before the executable callback. Use fenced code blocks for inline JSON, text, or HTML source. For outlines,<parameter>placeholders bind to the selected example row. -
step:Action|Action<LiveDocContext>|Func<Task>|Func<LiveDocContext, Task>— The step implementation. Use theLiveDocContextoverloads when you need to extract values from the title or read the bound inline description.
Returns
- Sync overloads:
void - Async overloads:
Task— must beawaited in the test method
Inline Markdown and JSON
Use the optional description when defining data must remain visible in the scenario narrative. Keep the title concise and pass exact raw JSON without serializing it again:
var request = new { value = 11 };
var myJsonVariable = JsonSerializer.Serialize(request);
Given(
title: "the increment request",
description: $"""
### Input
```json
{myJsonVariable}
```
""",
step: ctx =>
{
Assert.Contains(myJsonVariable, ctx.Step!.Description);
input = request.value;
});
The interpolated variable must be available before the step declaration is evaluated. A value produced inside the callback can be attached as evidence or shown by a later step, but cannot retroactively change this description.
Markdown and fenced code are rendered inline in Business and Developer views. HTML or script-like text is escaped rather than executed. Use attachments for downloadable artifacts and supplementary diagnostics, not as a replacement for defining request or expected-response content.
Step Keywords
Given()
Establishes preconditions — the initial state of the system.
Given("a registered user with email 'alice@example.com'", ctx =>
{
_user = new User { Email = ctx.Step!.Values[0].AsString() };
});
When()
Describes the action being performed — the behavior under test.
When("the user submits the login form", () =>
{
_result = _authService.Login(_user.Email, _password);
});
Then()
Asserts the expected outcome.
Then("login succeeds", () =>
{
Assert.True(_result.Success);
});
And()
Adds a continuation to the previous step type (Given, When, or Then).
Then("the user is redirected to the dashboard", () =>
{
Assert.Equal("/dashboard", _result.RedirectUrl);
});
And("a session token is issued", () =>
{
Assert.NotNull(_result.Token);
});
But()
Adds a negative or contrasting continuation to the previous step type.
Then("the order is placed", () =>
{
Assert.True(_order.IsConfirmed);
});
But("no confirmation email is sent to unverified users", () =>
{
Assert.False(_emailService.WasCalled);
});
Usage
Basic: Sync steps without context
Use the Action overload when values are already available (e.g., from ScenarioOutline parameters or class fields):
[Scenario]
public void Place_an_order()
{
Given("a customer with items in their cart", () =>
{
_cart = new ShoppingCart();
_cart.AddItem(new CartItem { Name = "Tea", Price = 12.99m });
});
When("they proceed to checkout", () =>
{
_order = _checkout.Process(_cart);
});
Then("the order is confirmed", () =>
{
Assert.True(_order.IsConfirmed);
});
And("the cart is emptied", () =>
{
Assert.Empty(_cart.Items);
});
}
Extracting values with context
Use the Action<LiveDocContext> overload to extract values from the step title, ensuring the documentation and the code stay in sync:
[Scenario]
public void Apply_discount_code()
{
Given("a cart with total '250.00'", ctx =>
{
_cart = new ShoppingCart { Total = ctx.Step!.Values[0].AsDecimal() };
});
When("the customer applies code 'SAVE20'", ctx =>
{
var code = ctx.Step!.Values[0].AsString();
_cart.ApplyDiscount(code);
});
Then("the total becomes '200.00'", ctx =>
{
Assert.Equal(ctx.Step!.Values[0].AsDecimal(), _cart.Total);
});
}
Async steps
Use Func<Task> or Func<LiveDocContext, Task> overloads for async operations. The test method must be async Task and each async step must be awaited:
[Scenario]
public async Task Create_user_via_api()
{
await Given("a valid user payload with name 'Alice'", async ctx =>
{
var name = ctx.Step!.Values[0].AsString();
_payload = new CreateUserRequest { Name = name };
});
await When("the API is called", async () =>
{
_response = await _httpClient.PostAsJsonAsync("/users", _payload);
});
Then("the response status is '201'", ctx =>
{
Assert.Equal(ctx.Step!.Values[0].AsInt(), (int)_response.StatusCode);
});
}
You can freely mix sync and async steps in the same scenario. Only async steps need await.
Named parameters
Use <name:value> syntax for self-documenting parameter extraction:
[Scenario]
public void Transfer_funds()
{
Given("an account with <balance:1000> dollars", ctx =>
{
var balance = ctx.Step!.Params["balance"].AsDecimal();
_account = new Account { Balance = balance };
});
When("transferring <amount:250> dollars", ctx =>
{
var amount = ctx.Step!.Params["amount"].AsDecimal();
_account.Transfer(amount);
});
Then("the balance is <remaining:750> dollars", ctx =>
{
var expected = ctx.Step!.Params["remaining"].AsDecimal();
Assert.Equal(expected, _account.Balance);
});
}
ScenarioOutline with placeholders
In [ScenarioOutline] methods, <paramName> segments in step titles are replaced with the current example's values in the formatted output:
[ScenarioOutline]
[Example("Australia", 100.00, "Free")]
[Example("New Zealand", 50.00, "Standard")]
public void Shipping_rates(string country, decimal total, string type)
{
Given("a customer from <country>", () =>
{
_cart = new ShoppingCart { Country = country };
});
When("the order totals <total>", () =>
{
_cart.Total = total;
_cart.Calculate();
});
Then("shipping is <type>", () =>
{
Assert.Equal(type, _cart.ShippingType);
});
}
Output for first example:
Scenario Outline: Shipping rates
Given a customer from Australia
When the order totals 100.00
Then shipping is Free
Multiple And/But steps
Chain multiple And() and But() steps after any primary step:
[Scenario]
public void Comprehensive_validation()
{
Given("a new user registration form", () =>
{
_form = new RegistrationForm();
});
When("submitting with email 'test@example.com'", ctx =>
{
_form.Email = ctx.Step!.Values[0].AsString();
_result = _validator.Validate(_form);
});
Then("email is valid", () =>
{
Assert.True(_result.EmailValid);
});
And("username is required", () =>
{
Assert.False(_result.UsernameValid);
});
And("password is required", () =>
{
Assert.False(_result.PasswordValid);
});
But("no error is shown for email", () =>
{
Assert.Null(_result.EmailError);
});
}
Formatted Output
Steps produce indented, Gherkin-style output in Test Explorer:
Feature: User Registration
Scenario: Comprehensive validation
Given a new user registration form
When submitting with email test@example.com
Then email is valid
and username is required
and password is required
but no error is shown for email
✓ 6 passing (12ms)
Given,When,Thenare indented under the scenarioAnd,Butappear as continuations with lowercase keywords- Quoted values have their quotes removed in the display
- Named parameter
<name:value>displays only the value - Outline
<placeholder>segments are replaced with example data
Caveats
- Steps are only available in
FeatureTest—SpecificationTestuses direct assertions in[Rule]methods instead. ctx.Stepmay be null if you call a step method using theActionoverload (withoutLiveDocContext). Use theAction<LiveDocContext>overload when you need value extraction.- Async steps must be
awaited — forgettingawaitcauses steps to execute out of order and may suppress exceptions. - Step title is the documentation — always embed meaningful values in the title string, not just in the lambda body.
See Also
FeatureTest— base class providing step methods- Value Extraction API —
Values,Params,As<T>()details - Context —
LiveDocContextandStepContextreference [Example]— data rows for ScenarioOutline- Your First Feature — tutorial with step-by-step walkthrough