Features
This chapter contains overview and examples of some Cucumber and Gherkin features.
Rule keyword
The purpose of the Rule keyword is to represent a business rule that should be implemented. It provides additional information for a feature. A Rule is used to group together several scenarios that belong to this business rule. A Rule should contain one or more scenarios that illustrate the particular rule.
You don't need additional work on the implementation side to support Rules. Let's take final example from Getting Started chapter and change the .feature file to:
Feature: Animal feature
Rule: Hungry cat becomes satiated
Scenario: If we feed a hungry cat it will no longer be hungry
Given a hungry cat
When I feed the cat
Then the cat is not hungry
Rule: Satiated cat remains the same
Scenario: If we feed a satiated cat it will not become hungry
Given a satiated cat
When I feed the cat
Then the cat is not hungry
Background keyword
Occasionally you’ll find yourself repeating the same Given steps in all the scenarios of a Feature.
Since it's repeated in every scenario, this is an indication that those steps are not essential to describe the scenarios, so they are incidental details. You can literally move such Given steps to background, by grouping them under a Background section.
Background allows you to add some context to the Scenarios following it. It can contain one or more steps, which are run before each scenario (but after any Before hooks).
Feature: Animal feature
Background:
Given a hungry cat
Rule: Hungry cat becomes satiated
Scenario: If we feed a hungry cat it will no longer be hungry
When I feed the cat
Then the cat is not hungry
Rule: Satiated cat remains the same
Background:
When I feed the cat
Scenario: If we feed a satiated cat it will not become hungry
When I feed the cat
Then the cat is not hungry
Background Steps indicated by > sign in the output by default.
In case Background is declared outside any Rule, it will be run on any Scenario. Otherwise, if Background is declared inside Rule, it will be run only for Scenarios inside this Rule and only after top-level Background statements, if any.
Tips for using Background
- Don’t use
Backgroundto set up complicated states, unless that state is actually something the client needs to know. - Keep your
Backgroundsection short. - Make your
Backgroundsection vivid, use colorful names, and try to tell a story. - Keep your
Scenarios short, and don’t have too many.
Clearly, example provided above doesn't need Background and was done for demonstration purposes only.
Scenario Outline keyword
The Scenario Outline keyword can be used to run the same Scenario multiple times, with different combinations of values:
Feature: Animal feature
Scenario Outline: If we feed a hungry animal it will no longer be hungry
Given a hungry <animal>
When I feed the <animal>
Then the <animal> is not hungry
Examples:
| animal |
| cat |
| dog |
| 🦀 |
And leverage regex support to match Steps:
use std::{convert::Infallible, time::Duration}; use async_trait::async_trait; use cucumber::{given, then, when, World, WorldInit}; use tokio::time::sleep; #[derive(Debug)] struct Cat { pub hungry: bool, } impl Cat { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, WorldInit)] pub struct AnimalWorld { cat: Cat, } #[async_trait(?Send)] impl World for AnimalWorld { type Error = Infallible; async fn new() -> Result<Self, Infallible> { Ok(Self { cat: Cat { hungry: false }, }) } } #[given(regex = r"^a (hungry|satiated) (\S+)$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when(regex = r"^I feed the (\S+)$")] async fn feed_cat(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; world.cat.feed(); } #[then(regex = r"^the (\S+) is not hungry$")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::run("/tests/features/book/features/scenario_outline.feature").await; }
Combining regex and FromStr
At parsing stage, <templates> are replaced by value from cells. That means you can parse table cells into any type, that implements FromStr.
Feature: Animal feature
Scenario Outline: If we feed a hungry animal it will no longer be hungry
Given a hungry <animal>
When I feed the <animal> <n> times
Then the <animal> is not hungry
Examples:
| animal | n |
| cat | 2 |
| dog | 3 |
| 🦀 | 4 |
use std::{convert::Infallible, str::FromStr, time::Duration}; use async_trait::async_trait; use cucumber::{given, then, when, World, WorldInit}; use tokio::time::sleep; #[derive(Debug)] struct Cat { pub hungry: bool, } impl Cat { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, WorldInit)] pub struct AnimalWorld { cat: Cat, } #[async_trait(?Send)] impl World for AnimalWorld { type Error = Infallible; async fn new() -> Result<Self, Infallible> { Ok(Self { cat: Cat { hungry: false }, }) } } enum State { Hungry, Satiated, } impl FromStr for State { type Err = &'static str; fn from_str(s: &str) -> Result<Self, Self::Err> { match s { "hungry" => Ok(Self::Hungry), "satiated" => Ok(Self::Satiated), _ => Err("expected hungry or satiated"), } } } #[given(regex = r"^a (\S+) (\S+)$")] async fn hungry_cat(world: &mut AnimalWorld, state: State) { sleep(Duration::from_secs(2)).await; match state { State::Hungry => world.cat.hungry = true, State::Satiated => world.cat.hungry = false, } } #[when(regex = r"^I feed the (?:\S+) (\d+) times?$")] async fn feed_cat(world: &mut AnimalWorld, times: usize) { sleep(Duration::from_secs(2)).await; for _ in 0..times { world.cat.feed(); } } #[then(regex = r"^the (\S+) is not hungry$")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::run("/tests/features/book/features/scenario_outline_fromstr.feature").await; }
Spoken languages
The language you choose for Gherkin should be the same language your users and domain experts use when they talk about the domain. Translating between two languages should be avoided.
This is why Gherkin has been translated to over 70 languages.
A # language: header on the first line of a .feature file tells Cucumber which spoken language to use (for example, # language: fr for French). If you omit this header, Cucumber will default to English (en).
# language: no
Egenskap: Animal feature
Eksempel: If we feed a hungry cat it will no longer be hungry
Gitt a hungry cat
Når I feed the cat
Så the cat is not hungry
In case most of your .feature files aren't written in English and you want to avoid endless # language: comments, use Cucumber::language() method to override the default language.
Scenario hooks
Before hook
Before hook runs before the first step of each scenario, even before Background ones.
use std::{convert::Infallible, time::Duration}; use async_trait::async_trait; use cucumber::WorldInit; use futures::FutureExt as _; use tokio::time; #[derive(Debug, WorldInit)] struct World; #[async_trait(?Send)] impl cucumber::World for World { type Error = Infallible; async fn new() -> Result<Self, Self::Error> { Ok(World) } } fn main() { World::cucumber() .before(|_feature, _rule, _scenario, _world| { time::sleep(Duration::from_millis(10)).boxed_local() }) .run_and_exit("tests/features/book"); }
⚠️ Think twice before using
Beforehook!
Whatever happens in aBeforehook is invisible to people reading.features. You should consider using aBackgroundas a more explicit alternative, especially if the setup should be readable by non-technical people. Only use aBeforehook for low-level logic such as starting a browser or deleting data from a database.
After hook
After hook runs after the last step of each Scenario, even when that step fails or is skipped.
use std::{convert::Infallible, time::Duration}; use async_trait::async_trait; use cucumber::WorldInit; use futures::FutureExt as _; use tokio::time; #[derive(Debug, WorldInit)] struct World; #[async_trait(?Send)] impl cucumber::World for World { type Error = Infallible; async fn new() -> Result<Self, Self::Error> { Ok(World) } } fn main() { World::cucumber() .after(|_feature, _rule, _scenario, _world| { time::sleep(Duration::from_millis(10)).boxed_local() }) .run_and_exit("tests/features/book"); }
CLI options
Library provides several options that can be passed to the command-line.
Use --help flag to print out all the available options:
cargo test --test <test-name> -- --help
Default output is:
cucumber 0.10.0
Run the tests, pet a dog!
USAGE:
cucumber [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
-v, --verbose Increased verbosity of an output: additionally outputs step's doc string (if present)
OPTIONS:
--color <auto|always|never> Coloring policy for a console output [default: auto]
-i, --input <glob> Glob pattern to look for feature files with. By default, looks for `*.feature`s
in the path configured tests runner
-c, --concurrency <int> Number of scenarios to run concurrently. If not specified, uses the value
configured in tests runner, or 64 by default
-n, --name <regex> Regex to filter scenarios by their name [aliases: scenario-name]
-t, --tags <tagexpr> Tag expression to filter scenarios by [aliases: scenario-tags]
Example with tag expressions for filtering Scenarios:
cargo test --test <test-name> -- --tags='@cat or @dog or @ferris'
Note: CLI overrides any configurations set in the code.
Customizing CLI options
CLI options are designed to be composable from the one provided by Parser::Cli, Runner::Cli and Writer::Cli.
You may also extend CLI options with custom ones, if you have such a need for running your tests. See a cli::Opts example for more details.