| Title: | Behavior-Driven Development for R |
|---|---|
| Description: | Write executable specifications in a natural language that describes how your code should behave. Write specifications in feature files using 'Gherkin' language and execute them using functions implemented in R. Use them as an extension to your 'testthat' tests to provide a high level description of how your code works. |
| Authors: | Jakub Sobolewski [aut, cre] |
| Maintainer: | Jakub Sobolewski <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 2.2.0 |
| Built: | 2026-10-02 13:15:10 UTC |
| Source: | https://github.com/jakubsob/cucumber |
A reporter that prints step-by-step progress to the console, mimicking testthat::ProgressReporter but with visibility into individual Gherkin steps.
Prints each step as it executes with a status indicator, grouped under scenario and feature headers similar to testthat's output:
✓ for passed steps (green)
✗ for failed/errored steps (red)
Feature: Addition
Scenario: Add two numbers
[x] Given I have entered 50 into the calculator
[x] Given I have entered 70 into the calculator
[x] When I press add
[x] Then the result should be 120 on the screen
Pass a reporter instance to test() or run():
# Step-level detail
cucumber::test("tests/acceptance", reporter = cucumber::CucumberProgressReporter$new())
# testthat reporters still work, reporting at the scenario level only
cucumber::test("tests/acceptance", reporter = testthat::ProgressReporter$new())
# Silent mode
cucumber::test("tests/acceptance", reporter = testthat::SilentReporter$new())
# From within a test file
cucumber::run(reporter = cucumber::CucumberProgressReporter$new())
Extend CucumberReporter to build your own:
HTMLReporter <- R6::R6Class(
"HTMLReporter",
inherit = cucumber::CucumberReporter,
public = list(
html_output = character(),
start_feature = function(feature_name) {
super$start_feature(feature_name)
self$html_output <- c(self$html_output, sprintf("<h2>%s</h2>", feature_name))
},
end_step = function(step) {
super$end_step(step)
status_class <- step$status %||% "passed"
self$html_output <- c(
self$html_output,
sprintf('<div class="step %s">%s %s</div>', status_class, step$keyword, step$text)
)
},
end_reporter = function() {
if (!is.null(super$end_reporter)) super$end_reporter()
writeLines(self$html_output, "test-report.html")
}
)
)
When end_step() is called, the step object contains:
keyword - The step keyword (Given, When, Then, etc.)
text - The step text
status - One of "passed", "failed", or "error"
error - The error object if the step failed or errored (NULL otherwise)
duration - Execution time in seconds
data_table - Data table if present (NULL otherwise)
docstring - Docstring if present (NULL otherwise)
testthat::Reporter -> cucumber::CucumberReporter -> CucumberProgressReporter
show_praiseWhether to show praise messages
step_countNumber of steps executed
step_passedNumber of steps that passed
step_failedNumber of steps that failed
current_scenarioName of the scenario currently executing
current_stepsSteps executed so far in the current scenario
failuresRecorded failures for the end-of-run summary
step_erroredWhether end_step already rendered an error this scenario
num_colorsTerminal color support captured at construction
reporter_max_docstring_linesMax docstring lines to print per step
reporter_max_table_linesMax data table rows to print per step
testthat::Reporter$.start_context()testthat::Reporter$cat_line()testthat::Reporter$cat_tight()testthat::Reporter$end_context()testthat::Reporter$end_context_if_started()testthat::Reporter$end_file()testthat::Reporter$end_test()testthat::Reporter$is_full()testthat::Reporter$local_user_output()testthat::Reporter$rule()testthat::Reporter$start_context()testthat::Reporter$start_file()testthat::Reporter$start_reporter()testthat::Reporter$update()new()
Initialize the reporter
CucumberProgressReporter$new(
show_praise = TRUE,
reporter_max_docstring_lines = getOption("cucumber.reporter_max_docstring_lines", Inf),
reporter_max_table_lines = getOption("cucumber.reporter_max_table_lines", Inf),
...
)show_praiseWhether to show praise (default TRUE)
reporter_max_docstring_linesMax docstring lines to print for a step before
truncating. Defaults to the cucumber.reporter_max_docstring_lines option, or
Inf (print in full) if unset.
reporter_max_table_linesMax data table rows to print for a step before
truncating. Defaults to the cucumber.reporter_max_table_lines option, or Inf
(print in full) if unset.
...Additional arguments passed to parent
start_feature()
Start a feature
CucumberProgressReporter$start_feature(feature_name)
feature_nameName of the feature
end_feature()
End the current feature
CucumberProgressReporter$end_feature()
start_test()
Start a test (called by testthat for each scenario)
CucumberProgressReporter$start_test(context, test)
contextTest context
testTest name (will be "Scenario: <name>")
add_result()
Add a result (warning, skip, failure, etc.)
CucumberProgressReporter$add_result(context, test, result)
contextTest context
testTest object
resultTest result object
start_step()
Start a step
CucumberProgressReporter$start_step(step)
stepPickle step object
end_step()
End a step and print its result
CucumberProgressReporter$end_step(step)
stepPickle step object with execution results
print_step_args()
Print a step's docstring and/or data table arguments, indented and
truncated to reporter_max_docstring_lines / reporter_max_table_lines
CucumberProgressReporter$print_step_args(step)
stepPickle step object
end_reporter()
Print summary at the end (called by testthat)
CucumberProgressReporter$end_reporter()
clone()
The objects of this class are cloneable with this method.
CucumberProgressReporter$clone(deep = FALSE)
deepWhether to make a deep clone.
## Not run: # Use with cucumber::test() cucumber::test( "tests/acceptance", reporter = cucumber::CucumberProgressReporter$new() ) # Or without praise messages cucumber::test( "tests/acceptance", reporter = cucumber::CucumberProgressReporter$new(show_praise = FALSE) ) ## End(Not run)## Not run: # Use with cucumber::test() cucumber::test( "tests/acceptance", reporter = cucumber::CucumberProgressReporter$new() ) # Or without praise messages cucumber::test( "tests/acceptance", reporter = cucumber::CucumberProgressReporter$new(show_praise = FALSE) ) ## End(Not run)
The following parameter types are available by default:
| Type | Description |
{int} |
Matches integers, for example 71 or -19. Converts value with as.integer. |
{float} |
Matches floats, for example 3.6, .8 or -9.2. Converts value with as.double. |
{word} |
Matches words without whitespace, for example banana (but not banana split). |
{string} |
Matches single-quoted or double-quoted strings, for example "banana split" or 'banana split' (but not banana split). Only the text between the quotes will be extracted. The quotes themselves are discarded.
|
To use custom parameter types, call define_parameter_type before cucumber::test is called.
define_parameter_type(name, regexp, transformer)define_parameter_type(name, regexp, transformer)
name |
The name of the parameter. |
regexp |
A regular expression that the parameter will match on. Note that if you want to escape a special character, you need to use four backslashes. |
transformer |
A function that will transform the parameter from a string to the desired type. Must be a function that requires only a single argument. |
An object of class parameter, invisibly. Function should be called for side effects.
define_parameter_type("color", "red|blue|green", as.character) define_parameter_type( name = "sci_number", regexp = "[+-]?\\\\d*\\\\.?\\\\d+(e[+-]?\\\\d+)?", transform = as.double ) ## Not run: #' tests/testthat/test-cucumber.R cucumber::define_parameter_type("color", "red|blue|green", as.character) cucumber::test(".", "./steps") ## End(Not run)define_parameter_type("color", "red|blue|green", as.character) define_parameter_type( name = "sci_number", regexp = "[+-]?\\\\d*\\\\.?\\\\d+(e[+-]?\\\\d+)?", transform = as.double ) ## Not run: #' tests/testthat/test-cucumber.R cucumber::define_parameter_type("color", "red|blue|green", as.character) cucumber::test(".", "./steps") ## End(Not run)
Hooks are blocks of code that can run at various points in the Cucumber execution cycle. They are typically used for setup and teardown of the environment before and after each scenario.
Where a hook is defined has no impact on what scenarios it is run for.
If you want to run a hook only before or after a specific scenario, use it's name to execute hook only for this scenario.
before(hook) after(hook)before(hook) after(hook)
hook |
A function that will be run. The function first argument is context and the scenario name is the second argument. |
Whatever happens in a before hook is invisible to people who only read the features.
You should consider using a background as a more explicit alternative, especially if the setup should be readable by non-technical people.
Only use a before hook for low-level logic such as starting a browser or deleting data from a database.
After hooks run after the last step of each scenario, even when the scenario failed or thrown an error.
## Not run: before(function(context, scenario_name) { context$session <- selenider::selenider_session() }) after(function(context, scenario_name) { selenider::close_session(context$session) }) after(function(context, scenario_name) { if (scenario_name == "Playing one round of the game") { context$game$close() } }) ## End(Not run)## Not run: before(function(context, scenario_name) { context$session <- selenider::selenider_session() }) after(function(context, scenario_name) { selenider::close_session(context$session) }) after(function(context, scenario_name) { if (scenario_name == "Playing one round of the game") { context$game$close() } }) ## End(Not run)
Internally used, package-specific options. They allow overriding the default behavior of the package.
The following options are available:
cucumber.indent
Regular expression for the indent of the feature files.
default: ^\\s{2}
cucumber.reporter_max_docstring_lines
Max docstring lines CucumberProgressReporter prints per step before truncating.
default: Inf
cucumber.reporter_max_table_lines
Max data table rows CucumberProgressReporter prints per step before truncating.
default: Inf
See base::options() and base::getOption() on how to work with options.
Call 'pending()' inside a step implementation to signal that the step is not yet implemented. The scenario will be reported as skipped rather than failed.
pending(message = "TODO")pending(message = "TODO")
message |
A message explaining why the step is pending. |
This is useful when you want to write your feature files first and implement the steps later, or when you want to temporarily disable a step without deleting its implementation.
given("I have {int} cucumbers in my basket", function(n, context) { pending("not yet implemented") })given("I have {int} cucumbers in my basket", function(n, context) { pending("not yet implemented") })
testthat contextIt's purpose is to be able to run Cucumber tests alongside testthat tests.
To do that, place a call to run() in one of the test-*.R files in your tests/testthat directory.
run(path = ".", filter = NULL, tags = NULL, reporter = get_reporter(), ...)run(path = ".", filter = NULL, tags = NULL, reporter = get_reporter(), ...)
path |
Path to the directory containing the |
filter |
If not NULL, only features with file names matching this regular expression will be executed. Matching is performed on the file name after it's stripped of ".feature". |
tags |
If not NULL, filter scenarios by tag expression string
(e.g., |
reporter |
Optional reporter instance (testthat::Reporter or cucumber::CucumberReporter). If NULL, will use reporter from package options if available. |
... |
Additional arguments passed to |
NULL, invisibly.
To get result and a report, use cucumber::test(), or inspect the result of testthat function call.
## Not run: #' tests/testthat/test-cucumber.R cucumber::run() ## End(Not run)## Not run: #' tests/testthat/test-cucumber.R cucumber::run() ## End(Not run)
Provide a description that matches steps in feature files and the implementation function that will be run.
given(description, implementation) when(description, implementation) then(description, implementation)given(description, implementation) when(description, implementation) then(description, implementation)
description |
A description of the step. Cucumber executes each step in a scenario one at a time, in the sequence you’ve written them in. When Cucumber tries to execute a step, it looks for a matching step definition to execute. Keywords are not taken into account when looking for a step definition.
This means you cannot have a Cucumber considers the following steps duplicates: Given there is money in my account Then there is money in my account This might seem like a limitation, but it forces you to come up with a less ambiguous, more clear domain language: Given my account has a balance of £430 Then my account should have a balance of £430 To pass arguments, description can contain placeholders in curly braces. To match: Given my account has a balance of £430 use: given("my account has a balance of £{float}", function(balance, context) {
})
If no step definition is found an error will be thrown. If multiple steps definitions for a single step are found an error will be thrown. |
implementation |
A function that will be run during test execution. The implementation function must always have the last parameter named If a step has a description If a table or a docstring is defined for a step, it will be passed as an argument after placeholder parameters
and before |
Placeholders in expressions are replaced with regular expressions that match values in the feature file. Regular expressions are generated during runtime based on defined parameter types.
The expression "I have {int} cucumbers in my basket" will be converted to
"I have [+-]?(?<![.])[:digit:]+(?![.]) cucumbers in my basket". The extracted value of {int}
will be passed to the implementation function after being transformed with as.integer.
To define your own parameter types use define_parameter_type.
A function of class step, invisibly. Function should be called for side effects.
given("I have {int} cucumbers in my basket", function(n_cucumbers, context) { context$n_cucumbers <- n_cucumbers }) given("I have {int} cucumbers in my basket and a table", function(n_cucumbers, table, context) { context$n_cucumbers <- n_cucumbers context$table <- table }) when("I eat {int} cucumbers", function(n_cucumbers, context) { context$n_cucumbers <- context$n_cucumbers - n_cucumbers }) then("I should have {int} cucumbers in my basket", function(n_cucumbers, context) { expect_equal(context$n_cucumbers, n_cucumbers) })given("I have {int} cucumbers in my basket", function(n_cucumbers, context) { context$n_cucumbers <- n_cucumbers }) given("I have {int} cucumbers in my basket and a table", function(n_cucumbers, table, context) { context$n_cucumbers <- n_cucumbers context$table <- table }) when("I eat {int} cucumbers", function(n_cucumbers, context) { context$n_cucumbers <- context$n_cucumbers - n_cucumbers }) then("I should have {int} cucumbers in my basket", function(n_cucumbers, context) { expect_equal(context$n_cucumbers, n_cucumbers) })
It runs tests from specifications in .feature files found in the path.
To run Cucumber tests alongside testthat tests, see cucumber::run().
test( path = "tests/acceptance", filter = NULL, tags = NULL, reporter = get_reporter(), env = NULL, load_helpers = TRUE, stop_on_failure = TRUE, stop_on_warning = FALSE, ... )test( path = "tests/acceptance", filter = NULL, tags = NULL, reporter = get_reporter(), env = NULL, load_helpers = TRUE, stop_on_failure = TRUE, stop_on_warning = FALSE, ... )
path |
Path to directory containing tests. |
filter |
If not NULL, only features with file names matching this regular expression will be executed. Matching is performed on the file name after it's stripped of ".feature". |
tags |
If not NULL, filter scenarios by tag expression string
(e.g., |
reporter |
Reporter to use to summarise output. Can be supplied
as a string (e.g. "summary") or as an R6 object
(e.g. See Reporter for more details and a list of built-in reporters. |
env |
Environment in which to execute the tests. Expert use only. |
load_helpers |
Source helper files before running the tests? |
stop_on_failure |
If |
stop_on_warning |
If |
... |
Additional arguments passed to |
Use a separate directory for your acceptance tests, e.g. tests/acceptance.
It's not prohibited to use tests/testthat directory, but it's not recommended as those tests
serve a different purpose and are better run separately, especially if acceptance tests take
longer to run than unit tests.
If you want to run Cucumber tests alongside testthat tests, you can use cucumber::run() in one of the test-*.R files in your tests/testthat directory.
Use setup-*.R
files for calling step(), define_parameter_type() and hook() to leverage testthat loading mechanism.
If your step(), define_parameter_type() and hook() are called from somewhere else, you are responsible for loading them.
Read more about testthat special files in the testthat documentation.
Use test-*.R files to test the support code you might have implemented that is used to run Cucumber tests.
Those tests won't be run when calling test(). To run those tests use
testthat::test_dir("tests/acceptance").
## Not run: cucumber::test("tests/acceptance") cucumber::test("tests/acceptance", filter = "addition|multiplication") # Tag expressions cucumber::test("tests/acceptance", tags = "@smoke") cucumber::test("tests/acceptance", tags = "@smoke and @fast") cucumber::test("tests/acceptance", tags = "@wip and not @slow") cucumber::test("tests/acceptance", tags = "(@smoke or @ui) and (not @slow)") ## End(Not run)## Not run: cucumber::test("tests/acceptance") cucumber::test("tests/acceptance", filter = "addition|multiplication") # Tag expressions cucumber::test("tests/acceptance", tags = "@smoke") cucumber::test("tests/acceptance", tags = "@smoke and @fast") cucumber::test("tests/acceptance", tags = "@wip and not @slow") cucumber::test("tests/acceptance", tags = "(@smoke or @ui) and (not @slow)") ## End(Not run)