Advanced Concepts in Reqnroll with Examples
For most of the scenarios, the basic concepts of Reqnroll are sufficient. However, there are some advanced concepts that can be used to write more complex scenarios. In this article, we will discuss some of these advanced concepts with examples.
Scenario Outline
The Scenario Outline keyword can be used to run the same scenario multiple times with different test data. The test data is provided in the Examples section of the scenario.
It's the equivalent of the theory attribute in xUnit.
Feature: Login Functionality
Scenario Outline: User Login with Different Credentials
Given the user is on the login page
When the user enters <username> and <password>
Then the login <status> should be displayed
Examples:
| username | password | status |
| user1 | pass123 | success |
| user2 | wrongpass | failure |
In the above example, the Scenario Outline is used to test the login functionality with different credentials. The Examples section provides the test data for the scenario.
public class LoginSteps
{
[Given(@"the user is on the login page")]
public void GivenTheUserIsOnTheLoginPage()
{
// Code to navigate to the login page
}
[When(@"the user enters (.*) and (.*)")]
public void WhenTheUserEntersUsernameAndPassword(string username, string password)
{
// Code to enter the username and password
}
[Then(@"the login (.*) should be displayed")]
public void ThenTheLoginStatusShouldBeDisplayed(string status)
{
// Code to verify the login status
}
}
Background
The Background keyword can be used to define a set of steps that are common to all scenarios in a feature. The steps defined in the Background section are executed before each scenario.
Feature: Product Inventory
Background:
Given there are 10 units of "Product A" in stock
Scenario: Selling a Product
When a customer purchases 2 units of "Product A"
Then the stock of "Product A" should be 8
Scenario: Restocking a Product
When the store manager adds 5 units of "Product A" to the inventory
Then the stock of "Product A" should be 15
Best Practices
Keep it Concise: The Background section should contain only essential setup steps to avoid excessive complexity.
Avoid Business Logic: The Background section is primarily for setup steps. Avoid including business logic or verifications; those should be placed in the individual scenarios.
Data tables
Feature: Shopping Cart
Scenario: Calculating Total Price with Discounts
Given the user has the following items in their shopping cart:
| Name | Quantity | Price |
| Apples | 5 | 2.00 |
| Bananas | 3 | 1.50 |
| Orange Juice| 2 | 3.50 |
When the user proceeds to checkout
Then the total price should be calculated as 15.50
But a discount of 10% should be applied
And the final payable amount should be 13.95
In the above example, the data table is used to provide the test data for the scenario.
public class ShoppingCartSteps
{
[Given(@"the user has the following items in their shopping cart:")]
public void GivenTheUserHasTheFollowingItemsInTheirShoppingCart(Table table)
{
// Code to add items to the shopping cart
}
[When(@"the user proceeds to checkout")]
public void WhenTheUserProceedsToCheckout()
{
// Code to proceed to checkout
}
[Then(@"the total price should be calculated as (.*)")]
public void ThenTheTotalPriceShouldBeCalculatedAs(decimal totalPrice)
{
// Code to verify the total price
}
[Then(@"a discount of (.*) should be applied")]
public void ThenADiscountOfShouldBeApplied(decimal discount)
{
// Code to verify the discount
}
[Then(@"the final payable amount should be (.*)")]
public void ThenTheFinalPayableAmountShouldBe(decimal payableAmount)
{
// Code to verify the payable amount
}
}
Example 4: Using Tags
Overview Tags are labels or markers that can be assigned to features or scenarios to categorize and filter them. Tags are prefixed with the @ symbol and can be used for various purposes, such as test categorization, conditional execution, or documentation.
Syntax Tags can be added to the Feature, Scenario, or Scenario Outline sections by prefixing them with the @ symbol. Multiple tags can be applied to a single feature or scenario.
@Permissions
Feature: Access to the command's details
@LoggedIn
Scenario: User can access the command's details
Given the user is logged in
When the user clicks on the View Details button
Then the command details should be displayed
@LoggedOut
Scenario: User cannot access the command's details
Given the user is logged out
When the user clicks on the View Details button
Then the user should be redirected to the login page
Usage in Test Execution During test execution, tags can be used to include or exclude specific scenarios. For example, using Reqnroll's test runner commands:
To run scenarios with a specific tag:
dotnet test --filter "Tags=Permissions"
To exclude scenarios with a specific tag:
dotnet test --filter "Tags!=Permissions"
Example 5: Using Hooks
hooks provide a way to execute code before and after various events in the test execution lifecycle. This allows you to set up or clean up datas, perform additional logging, or modify behavior around scenarios.
Types of Hooks
BeforeScenario: Executes before each scenario.
AfterScenario: Executes after each scenario.
BeforeFeature: Executes before each feature.
AfterFeature: Executes after each feature.
BeforeTestRun: Executes once before the entire test run.
AfterTestRun: Executes once after the entire test run
Parameterized Hooks
Hooks can take parameters to access information about the scenario or feature being executed.
[BeforeScenario]
public void BeforeScenario(ScenarioContext scenarioContext)
{
// Access scenario information
var scenarioTitle = scenarioContext.ScenarioInfo.Title;
}
Conditional Execution
You can use conditional logic within hooks to execute specific actions based on certain conditions.
[BeforeScenario("myTag")]
public void BeforeScenarioWithTag()
{
// Code to be executed before scenarios with the "myTag" tag
}
Execution Order
Hooks are executed in a predefined order. For example, BeforeTestRun hooks are executed first, followed by BeforeFeature hooks, BeforeScenario hooks, scenario execution, and then the corresponding After hooks.
Concrete Example
Feature: Order Processing
@InStock
Scenario: Process Order for In-Stock Item
Given there are 10 units of "Product A" in stock
When the user places an order for 2 units of "Product A"
Then the order should be processed successfully
@OutOfStock
Scenario: Process Order for Out-of-Stock Item
Given there are 0 units of "Product B" in stock
When the user places an order for 1 unit of "Product B"
Then the user should be notified of the out-of-stock status
in the above example, we can use hooks to set up the initial context for the scenarios.
Hooks are implemented as methods in a separate class or within the same class as step definitions. But it is recommended to keep them in a separate class for a better organization of the code.
OrderProcessingHooks.cs
[Binding]
public class OrderProcessingHooks
{
[BeforeScenario("InStock")]
public void SetupInStockProduct()
{
// Code to set up the initial context for the scenario
// Like creating a product in the database
}
[BeforeScenario("OutOfStock")]
public void SetupOutOfStockProduct()
{
// Code to set up the initial context for the scenario
// Empty the stock of the product
}
[AfterScenario("InStock", "OutOfStock")]
public void AfterTest()
{
// Code to run after any scenario with the @InStock or @OutOfStock tag
Console.WriteLine("Scenario execution completed.");
}
}
OrderProcessingStepDefinition.cs
[Binding]
public class OrderProcessingSteps
{
[Given(@"there are (.*) units of ""(.*)"" in stock")]
public void GivenThereAreUnitsOfInStock(int units, string productName)
{
// Code to set up the initial context for the scenario
}
[When(@"the user places an order for (.*) units of ""(.*)""")]
public void WhenTheUserPlacesAnOrderForUnitsOf(int units, string productName)
{
// Code to place the order
}
[Then(@"the order should be processed successfully")]
public void ThenTheOrderShouldBeProcessedSuccessfully()
{
// Code to verify the order status
}
[Then(@"the user should be notified of the out-of-stock status")]
public void ThenTheUserShouldBeNotifiedOfTheOut_Of_StockStatus()
{
// Code to verify the out-of-stock status
}
}
Force execution order
If you want to force the execution of an hook's method, you can use the Order property of the Binding attribute.
[Binding]
public class OrderProcessingHooks
{
[BeforeScenario(Order = 1)]
public void DeleteCreatedStock()
{
// Code to set up the initial context for the scenario
// Empty the stock of the product
}
}
In the case below the method DeleteCreatedStock will be executed despite the associated scenario is failed.