Skip to content

Scenario Steps Definition

Wojtek edited this page Oct 28, 2019 · 35 revisions

Page version: 3.x / 2.x / 1.x

Supported step definitions

LightBDD offers various ways to define scenario steps as well as support various step definitions to best balance scenario clarity with development needs.

All currently supported step definitions can be found by using LightBDD.Framework.Scenarios namespace. When used, extension methods defined in the namespace will be will become available for the Runner property.

Basic scenarios

Namespace: LightBDD.Framework.Scenarios

Basic scenarios offer the best clarity at the cost of functionality:

Runner.RunScenario(

	Given_the_user_is_about_to_login,
	Given_the_user_entered_valid_login,
	Given_the_user_entered_valid_password,
	When_the_user_clicks_login_button,
	Then_the_login_operation_should_be_successful,
	Then_a_welcome_message_containing_user_name_should_be_returned);

/* ... */
private void Given_the_user_is_about_to_login() { /* ... */ }

Scenario steps are defined as an array of delegates to step methods, where each method:

  • is a test class instance or a static method,
  • is returning void,
  • is parameterless,
  • cannot be a lambda expression nor anonymous method,
  • does not support contextual scenarios.

An example of basic scenarios can be found here.

Extended scenarios

Namespace: LightBDD.Framework.Scenarios

Extended scenarios offer more flexibility than basic equivalents:

  • they support parameterized steps,
  • they support contextual scenarios.
Runner.RunScenario(

	given => Product_is_available_in_product_storage("wooden desk"),
	and   => Product_is_available_in_product_storage("wooden shelf"),
	when  => Customer_buys_product("wooden desk"),
	and   => Customer_buys_product("wooden shelf"),
	then  => An_invoice_should_be_sent_to_the_customer(),
	and   => The_invoice_should_contain_product_with_price_of_AMOUNT("wooden desk", 62),
	and   => The_invoice_should_contain_product_with_price_of_AMOUNT("wooden shelf", 37));

/* ... */
private void Product_is_available_in_product_storage(string product)
{ /* ... */ }
Runner.RunScenario(

	_ => Given_product_is_available_in_product_storage("wooden desk"),
	_ => Given_product_is_available_in_product_storage("wooden shelf"),
	_ => When_customer_buys_product("wooden desk"),
	_ => When_customer_buys_product("wooden shelf"),
	_ => Then_an_invoice_should_be_sent_to_the_customer(),
	_ => Then_the_invoice_should_contain_product_with_price_of_AMOUNT("wooden desk", 62),
	_ => Then_the_invoice_should_contain_product_with_price_of_AMOUNT("wooden shelf", 37));

/* ... */
private void Given_product_is_available_in_product_storage(string product)
{ /* ... */ }

The two above examples are equivalent.

Scenario steps are defined as an array of single parameter lambda expressions, where for each expression:

  • lambda parameter defines the step type (given, when, then, etc.), unless a single character name is used (such as _ or x), then step type is determined from the method name,
  • lambda expression is a method call expression, where called method name corresponds to the step name,
  • step method can accept any amount of parameters of any type, as long as they are not ref nor out,
  • step type, step method name and passed parameters forms a step name.

The step type can be determined here in two ways:

  • from a lambda parameter given => ... which gives more control of defining step type, but leaves the methods with less clear names,
  • from the method name _ => Given_... or x => Given_... which gives a bit less control of the step type, but makes methods very clear.

Because of the higher method clarity and Steps auto-grouping feature, the latter approach is suggested as the best.

The rules for formatting parameterized step names are described in Formatting Parameterized Steps section.

The example of the extended, parameterized scenario can be found here.

Fluent scenarios

Namespace: LightBDD.Framework.Scenarios

Scenarios can be also defined wit a fluent API, allowing to mix basic, extended, compact, async and sync steps, however at the cost of test readability.
The use case for fluent scenario definitions is when the scenario is constructed in a programmatic manner (like when a number of steps vary on input data or other dynamic reasons).

Note: Scenarios defined fluently can have async void steps, unlike scenarios run with Runner.RunsScenario() method.

Example code:

using LightBDD.Framework;
using LightBDD.Framework.Scenarios;
using LightBDD.NUnit3;

/* .. */

[Label("Ticket-14")]
[Scenario]
public async Task Browsing_invoices()
{
    await Runner
        .AddAsyncSteps(
            _ => Given_invoice("Invoice-1"),
            _ => Given_invoice("Invoice-2"))
        .AddSteps(
            When_I_request_all_historical_invoices)
        .AddSteps(
            _ => Then_I_should_see_invoices("Invoice-1", "Invoice-2"))
        .RunAsync();
}

The fluent scenario definition is:

  • initiated with any .AddSteps() / .AddAsyncSteps() method that can be called directly on Runner or Runner.WithContext(/* ... */),
  • finished with .RunAsync() that executes the scenario.

Note: The basic syntax is available only if no Runner.WithContext(/* ... */) was used.

To see example usage, please check Invoice_history_feature.cs.

Compact scenarios

Namespace: LightBDD.Framework.Scenarios

The compact steps syntax is designed to create self-contained scenarios.

The compact scenarios can be useful to produce more low-level, unit-test like scenarios, where decreased readability caused by keeping step implementation details within the scenario method is an acceptable trade-off to the compact nature of the scenario.

Example code:

using LightBDD.Framework;
using LightBDD.Framework.Scenarios;

/*...*/

[Scenario]
public async Task Adding_numbers()
{
    Calculator calc = null;
    var result = 0;

    await Runner
        .AddStep("Given calculator", _ => calc = new Calculator())
        .AddStep("When I add two numbers", _ => result = calc.Add(3, 5))
        .AddStep("Then I should get an expected result", _ => Assert.Equal(8, result))
        .RunAsync();
}

Compact step definition:

  • it can be used with composite steps and fluent scenarios,
  • AddStep() / AddAsyncStep() adds one step at a time, where step consists of textual name and action to execute,
  • can be used with contextual scenarios/composites, where context is accessible by step action argument.

Please note that steps auto-grouping behavior is applicable to compact steps, which means that provided step types will be parsed the same way as for other syntaxes.

Since LightBDD 3.1.1, the name parsing mechanism has been improved to:

  • replace \t and new line characters with white space,
  • remove any other control characters,
  • trim any leading and following white characters,
  • do not perform any additional formatting such as replacing _ with space.

Contextual scenarios

Namespace: LightBDD.Framework.Scenarios

Normally, the scenarios are executing with no additional context and the step methods are defined in the test class. Sometimes, it is required however to execute the scenarios with an additional context:

  • if the test project is structured in a way that the step methods are defined in another class while the test class contains only scenario methods,
  • if the test framework offers the ability to run tests in parallel but the test methods share the same test class instance (like it is with NUnit3 or was with deprecated MbUnit).

Assuming that we have MyContext class, to make a scenario running with an instance of this context, one of following method has to be used:

Runner.WithContext<MyContext>().RunScenario(...);
Runner.WithContext(myContext).RunScenario(...);
Runner.WithContext(() = > new MyContext("abc")).RunScenario(...);

Please note that instances of the context will be created just before scenario execution when LightBDD controls the creation of the context.

Disposable context

The LightBDD can dispose the context implementing IDisposable interface when it is no longer needed.

The WithContext methods have been extended with new behavior where:

  • the .WithContext<MyContext>() will always dispose disposable context instances,
  • the .WithContext(new MyContext()) will not dispose context by default as instance is passed explicitly by caller. However, as the method has now an optional parameter bool takeOwnership = false, it is possible to make context automatically disposed by calling .WithContext(new MyContext(), true),
  • the .WithContext(() => new MyContext()) will dispose context by default as factory method is being passed indicating that it will produce a new instance on each call. Similarly to method above, it is possible to change default behavior and make context not disposed by calling .WithContext(() => new MyContext(), false).

Dependency injection

Finally, the LightBDD supports DI containers and dependency injection for the context instances, where the WithContext<T>() methods have been modified to use DI container to instantiate the T instance.

To learn more about dependency injection in LightBDD, please read DI Containers section.

Extended scenarios with scenario context

The extended scenario syntax works very well with contextual scenarios, allowing them to execute steps within additional context.

Runner.WithContext<ContactsManagementContext>().RunScenario(

    c => c.Given_my_contact_book_is_filled_with_contacts(),
    c => c.When_I_remove_one_contact(),
    c => c.Then_the_contact_book_should_not_contain_removed_contact_any_more(),
    c => c.Then_the_contact_book_should_contains_all_other_contacts());

/* ... */
public class ContactsManagementContext
{
    public void Given_my_contact_book_is_filled_with_contacts()
    {
        /* ... */
    }
    /* ... */
}

Please note that the lambda parameter has a single letter c name in the above example. Any single letter names, including _ will not participate in the step name displayed in progress and final reports.

Runner.WithContext<SpeditionContext>().RunScenario(

    given => given.There_is_an_active_customer_with_id("ABC-123"),
    and => and.The_customer_has_product_in_basket("wooden shelf"),
    and => and.The_customer_has_product_in_basket("wooden desk"),
    when => when.The_customer_payment_finalizes(),
    then => then.Product_should_be_dispatched_to_the_customer("wooden shelf"),
    and => and.Product_should_be_dispatched_to_the_customer("wooden desk"));

/* ... */
public class SpeditionContext
{
    public void There_is_an_active_customer_with_id()
    {
        /* ... */
    }
    /* ... */
}

Scenario steps are defined in the same way as for regular Extended scenarios and have the same constraints, except that:

  • lambda expression parameter represents also the instance of the scenario context,
  • called step methods should be declared in context type (while it is more OO way and it is recommended, it is not enforced though),
  • context is instantiated before scenario execution and passed to each lambda delegate (it is shared with all steps of this scenario).

The example of contextual scenarios can be found here and here.

Steps auto-grouping

The step type (given, when, then, setup) is automatically detected for steps written in:

It means that scenarios displayed during execution or presented in report files would be presented consistently. If consecutive steps have the same step type, all but the first step would be renamed to start with AND.

With this behavior, the following scenario:

Runner.RunScenario(

	Given_the_user_is_about_to_login,
	Given_the_user_entered_valid_login,
	Given_the_user_entered_valid_password,
	When_the_user_clicks_login_button,
	Then_the_login_operation_should_be_successful,
	Then_a_welcome_message_containing_user_name_should_be_returned);

would result with output like:

GIVEN the user is about to login
AND the user entered valid login
AND the user entered valid password
WHEN the user clicks login button
THEN the login operation should be successful
AND a welcome message containing user name should be returned

Please note that auto-grouping would not be applied to scenarios like:

Runner.RunScenario(

	given => the_user_is_about_to_login(),
	given => the_user_entered_valid_login(),
	given => the_user_entered_valid_password(),
	when  => the_user_clicks_login_button(),
	then  => the_login_operation_should_be_successful(),
	then  => a_welcome_message_containing_user_name_should_be_returned());

as here, the user has ability to specify the step type explicitly.

Finally, it is possible to configure auto-grouping behavior on LightBDD initialization. The LightBDD configuration mechanism is described in section LightBDD Configuration. It is possible to customize auto-grouping behavior with the following code:

configuration.StepTypeConfiguration()
	.UpdatePredefinedStepTypes("given","when","then","but")
	.UpdateRepeatedStepReplacement("and also");

It is possible to disable the auto-grouping by passing an empty string as RepeatedStepReplacement.

Asynchronous scenario step execution

All currently present scenario types support asynchronous step execution. The following table summarizes the differences between synchronous and asynchronous methods:

  • void RunScenario(...)
    Runs scenario synchronously. Does not allow asynchronous steps.

    Expected step signature: void Given_...()
    Expected Runner usage: Runner.RunScenario(...)
    Availability: basic / extended scenarios

  • Task RunScenarioAsync(...)
    Runs scenario asynchronously. Expects all steps to return Task. The scenario execution has to be awaited.
    The usage example of RunScenarioAsync(...) can be found here.

    Expected step signature: Task Given_...()
    Expected Runner usage: await Runner.RunScenarioAsync(...) or return Runner.RunScenarioAsync(...)
    Availability: basic / extended scenarios

  • Task RunAsync(...) (fluent syntax)
    Runs scenario asynchronously but allows mixing of synchronous (void) and asynchronous (async void / Task) steps. The scenario execution has to be awaited.
    The usage example of RunAsync(...) can be found here.

    Expected step signature: void Given_...() or async void Given_...() or Task Given...()
    Expected Runner usage: await Runner.AddSteps(...).RunAsync(...) or return Runner.AddSteps(...).RunAsync(...)
    Availability: basic / extended scenarios

Parameterized scenarios

There are use cases where a given scenario has to be executed against a set of different input parameters. The testing frameworks usually can parameterize test methods and declare input values with special attributes.

LightBDD preserves that capability and allows to use of test framework specific mechanisms to parameterize scenarios.

Below, there are a few examples of parameterized scenarios:

  • LightBDD.NUnit3
    [Scenario]
    [TestCase("EN")]
    [TestCase("PL")]
    [TestCase("DE")]
    public void Displaying_home_page_in_LANG(string lang)
    {
        Runner.RunScenario(
            _ => Given_a_customer_with_LANG_language_selected(lang),
            _ => When_the_customer_opens_the_home_page(),
            _ => Then_header_should_display_LANG_language(lang),
            _ => Then_page_title_should_be_translated(),
            _ => Then_all_menu_items_should_be_translated()
            );
    }
  • LightBDD.XUnit2
    [Scenario]
    [InlineData("EN")]
    [InlineData("PL")]
    [InlineData("DE")]
    public void Displaying_home_page_in_LANG(string lang)
    {
        Runner.RunScenario(
            _ => Given_a_customer_with_LANG_language_selected(lang),
            _ => When_the_customer_opens_the_home_page(),
            _ => Then_header_should_display_LANG_language(lang),
            _ => Then_page_title_should_be_translated(),
            _ => Then_all_menu_items_should_be_translated()
            );
    }
  • LightBDD.MsTest2
    [Scenario]
    [DataRow("EN")]
    [DataRow("PL")]
    [DataRow("DE")]
    public void Displaying_home_page_in_LANG(string lang)
    {
        Runner.RunScenario(
            _ => Given_a_customer_with_LANG_language_selected(lang),
            _ => When_the_customer_opens_the_home_page(),
            _ => Then_header_should_display_LANG_language(lang),
            _ => Then_page_title_should_be_translated(),
            _ => Then_all_menu_items_should_be_translated()
            );
    }

To learn how to parameterized scenarios are rendered, please read Formatting Parameterized Steps > Formatting Parameterized Scenarios.

Continue reading: Composite Steps Definition

Clone this wiki locally
You can’t perform that action at this time.