8

Behat by default looks for the step definitions in file named FeatureContext (all steps in one file).
Having a lot of steps, it's hard to maintain such a big file.

I'd like to have one definition file per feature file.

How can I have step definitions in external files?

e.g.

homepage.feature
HomepageContext extends FeatureContext
Sfisioza
  • 3,441
  • 5
  • 38
  • 53

3 Answers3

25

Behat has multiple options for you to split up your FeatureContext into multiple classes. First, you can use old-school php5 inheritance. If the inheritance is not what you want, Behat also supports subcontexts: "Using Subcontexts".

Next, if you want to name your class differently than FeatureContext, you can redefine that in "Context Configuration" section of your behat.yml config file.

This way, you could split common definitions and hooks into separate classes and use them in other feature suites as well with either subcontexting or inheritance.

But your question also asks:

I'd like to have one definition file per feature file.

This request is totally wrong. Behat and Scenario BDD is all about describing your application behavior in business terms and creating test dictionary for described behaviors. Keeping that in mind, you logically couldn't have multiple different dictionaries for one feature-set. By writing step definitions, you're telling Behat what that Given I am on "/news" means. And when you want that step to mean different things from feature to feature - you're doing it wrong.

Behat consists of 2 main and enough separate concepts:

  1. *.feature files, written in Gherkin language. Those files should be self-descriptive. Means, that they should provide all the information for the reader in order to understand them. Gherkin is not a new programming language for your functional tests, it's just a markdown for your user-stories!
  2. FeatureContext.php classes, describes how Behat should test your features. It defines application-wide dictionary to be used with whole application feature-suite. This is a programming bridge between your markdown-like user-stories and actual functional tests.

And you shouldn't mess this things up. Single feature suite should have single steps dictionary (definitions). But you could use single dictionary in more than one feature suite thanks to inheritance and subcontexts. And yes, you can split single suite dictionary into multiple php classes ;-)

everzet
  • 1,715
  • 13
  • 9
  • I understand where you coming from but are you forgetting the pain of maintaining your specs? Sfisioza's point (I guess) was about this.If someone's got a complex system one might experience such pain of going through monolithic FeatureContext.php – RVM Apr 24 '14 at 10:47
  • You seem to ignore the fact that your definition file will soon be 14 000 lines long, dictionary or not (And I really see your point here, and agree fully that you should see it as a dictionary). You also miss the point where you don't always need the full feature dictionary. Yes the definition should always be the same, but do I even need the definition? And how can I be sure the definition hasn't been already written in a slightly different manner? Creating order by splitting up files would surely help here. – David Nov 18 '18 at 19:45
6

Use class inheritance and separate contexts.

# /features/contexts/
AbstractContext extends BehatContext {}
FeaturenameContext extends AbstractContext {}

Then in /feature/FeatureContext.php import the context files:

/**
 * Initializes context.
 * Every scenario gets it's own context object.
 *
 * @param array $parameters context parameters (set up via behat.yml)
 */
public function __construct(array $parameters) {

    // import all context classes from context directory, except the abstract one

    $filesToSkip = array('AbstractContext.php');

    $path = dirname(__FILE__) . '/../contexts/';
    $it = new RecursiveDirectoryIterator($path);
    /** @var $file  SplFileInfo */
    foreach ($it as $file) {
        if (!$file->isDir()) {
           $name = $file->getFilename();
           if (!in_array($name, $filesToSkip)) {
               $class = pathinfo($name, PATHINFO_FILENAME);
               require_once dirname(__FILE__) . '/../context/' . $name;
               $this->useContext($class, new $class($parameters));
           }
        }
    }
}
takeshin
  • 44,484
  • 28
  • 112
  • 160
0

One solution is horizontal reusability with subContexts. Use a subContext for each "feature group".

class FeatureContext extends BehatContext
{

    public function __construct(array $context_parameters)
    {
        $this->useContext('math_context', new MathContext());
        $this->useContext('bash_context', new BashContext());
    }
}
johnlemon
  • 18,991
  • 36
  • 111
  • 174