Search code examples
tddbddbehat

Using Context in Behat - The right approach


What is the right approach for using FooContext classes in behat?

What it is by the docs:

A simple mnemonic for context classes is: “testing features in a context”. (...), the way you will test those features pretty much depends on the context you test them in.

In the docs FeatureContext seems for me only to be a dummy context file so that you can fast create a behat test.

The context class should be called FeatureContext. It’s a simple convention inside the Behat infrastructure. FeatureContext is the name of the context class for the default suite.

Its not saying me directly that it has to be a context file per feature.

The only real other examples in the docs are contexts like ApiContext or WebContext.

default:
    suites:
        web_features:
            paths:    [ %paths.base%/features/web ]
            contexts: [ WebContext ]
        api_features:
            paths:    [ %paths.base%/features/api ]
            contexts: [ ApiContext ]

What i also found was a CommandFeature and another CommandLineProcessContext.

So if i have many features to test, a context file would blow up very fast.

Then i see a more likely a context file per feature example by Marco Pivetta using an Aggregate as Context.

Is it a good idea to have a single context file per feature foo.feature? Or are context files thought to be environmental contexts like in the docs ApiContext or WebContext?


Solution

  • Usually I prefer to divide context and features as follows:

    • One folder for each domain subject
    • One feature / context file for each action you can perform onto this subjects

    That way you'll end up with a single suite using more than one context but with all context divided with a single responsibility.

    Quick example

    Features
    |--- User
    |----- login.feature
    |----- change_password.feature
    |----- impersonate.feature
    |----- ban.feature
    |----- ...
    |--- ...
    |--- Order
    |----- checkout.feature
    |----- cancel.feature
    ...
    Context
    |--- User
    |---- LoginContext
    |---- ChangePasswordContext
    |---- ImpersonateContext
    |---- BanContext
    |---- ...
    |--- Order
    |---- CheckoutContext
    |---- CancelContext
    

    Each suite is composed with many context (for example, every time you need to login to check a behavior, you'll include LoginContext in your suite).

    default:
      suites:
        suite_name:
          paths:
            - '%paths.base%/Path/To/Feature/File'
          contexts:
            - Path\To\Context\LoginContext
            - Path\To\A\SecondContext
            - ...
    

    This approach has many advantages in terms of maintainability, intuitiveness and so on.

    If you would like to have a more generic panoramic on this subject, you can check slides from my talk at Symfony day 2017 in Milan