Skip to content

Contextual step-definitions #1792

@mitchgrout

Description

@mitchgrout

🤔 What's the problem you're trying to solve?

I have multiple features that use rather generic statements, but in terms of implementation end up being different. For example, I may have:

# x.feature
Given I am on the X workflow
When I complete all questions
Then the submit button becomes enabled

# y.feature
Given I am on the Y workflow
When I complete all questions
Then the submit button becomes enabled

"Completeting all questions" could range from simply ticking some boxes, entering some text, or selecting a field, and varies by-workflow.

Since we can only implement a single When "I complete all questions", I would need to have the implementation for all my different workflows bundled into it.

✨ What's your proposed solution?

An additional selector on step-definitions to allow the runtime to select them based off a predicate:

# step_definitions/workflows/x.rb
Given("I am on the X workflow") do
  @workflow = :X
  ...
end

# DSL-style
Contextually { @workflow == :X }
When("I complete all questions") do
  ...
end

# Attribute-style
When("I complete all questions", when: -> { @workflow == :X }) do
  ...
end

⛏ Have you considered any alternatives or workarounds?

The simplest and most approachable solution would be to simply have different step definitions:

# step_definitions/workflows/x.rb
When "I complete all questions for the X workflow" do
  ...
end

# step_definitions/workflows/y.rb
When "I complete all questions for the Y workflow" do
  ...
end

...but now, I'd need to update the .feature to compensate -- I don't want feature writers to worry about this, so it's low on my list of solutions.

The more standard solution would be to branch inside of the When:

# step_definitions/workflows.rb
When "I complete all questions" do
  case @workflow
  in :X then ...
  in :Y then ...
  end
end

...but, as the number of cases increases, this gets pretty bloated. I could factor out the branch arms to helper functions on modules:

# step_definitions/x.rb
module Workflows::X
  def self.complete! = ...
end

# step_definitions/workflows.rb
When "I complete all questions" do
# const-lookup, avoids constantly updating this,
# but can make it hard to understand how to add new workflows
  Workflows.const_get(@workflow).complete!

# case-ing, requires manual updating,
# but is clearer that these methods are really just step definitions
  case @workflow
  in :X then Workflows::X.complete!
  in :Y then Workflows::Y.complete!
  end
end

...but both of these have drawbacks also.

📚 Any additional context?

I recognize that this makes step-resolution by the runtime harder, and can add new failure modes. For example, if the predicate were to raise, would we propagate the error, or simply mark the step as non-applicable? If the latter, is this going to be obvious to the developer?

I also do not believe any of the other language bindings have a feature like this, which makes it an even more far-fetched proposal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions