Tame Behat with the Brand New Symfony Extension
Tags: php, bdd, behat, symfony
Published: 2020-01-13 20:36:11

---
title: Tame Behat with the Brand New Symfony Extension
published: true
date: 2019-02-11 00:00:00 UTC
tags: [php, bdd, behat, symfony]
canonical_url: https://kamilkokot.com/tame-behat-with-the-brand-new-symfony-extension/
---

This blogpost has been originally published at [kamilkokot.com](https://kamilkokot.com/tame-behat-with-the-brand-new-symfony-extension/).

Over two years after the first release of this [Friends of Behat](https://github.com/FriendsOfBehat)’s [Symfony Extension](https://github.com/FriendsOfBehat/SymfonyExtension), I’m happy to announce the availability of the second major version and write more about it.

It is the already battle-tested solution with [Sylius](https://github.com/Sylius/Sylius/pull/10102) on 1300+ scenarios containing 14000+ steps, getting traction in projects like [Akeneo](https://github.com/akeneo/pim-community-dev/pull/9416) or [Monofony](https://github.com/Monofony/SymfonyStarter/pull/103).

The mission behind it is to simplify the process of implementing [Behat](http://behat.org/en/latest/) contexts so that you can focus on the communication with stakeholders and writing down business features instead of figuring out low-level testing infrastructure details.

### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>TL;DR

- Support for Symfony’s autowiring and autoconfiguration
- Symfony Flex contrib recipe to make installation easier
- Zero-configuration for most Symfony 3 and 4 based apps
- Unified application configuration and dotenv file handling (similar to the PHPUnit one)
- Integration with Mink, available out of the box
- Simplified usage, reduced the number of extensions from three to one
- Improved developer experience with more descriptive exceptions and more predictable overall behaviour

If you want to try it first, [dive straight into the documentation](https://github.com/FriendsOfBehat/SymfonyExtension#documentation).

I am working on an entry-level tutorial for SymfonyExtension v2, make sure to [follow me on Twitter](https://twitter.com/intent/follow?region=follow_link&screen_name=pamilme&source=followbutton&variant=2.0)if you do not want to miss it.

### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Support for Symfony’s autowiring and autoconfiguration

Ever since I have started working with Behat and Symfony on more complex projects, the number of configuration changes needed to implement even simple steps was an issue and it was going worse and worse.

Whether it was `Behat/Symfony2Extension` or `FriendsOfBehat/SymfonyExtension`, injecting a dependency into a context required too much hassle and pulled me away from the domain being modelled.

The common use case is to have a context which implements a step like `Given I am a logged in customer` that is used in every suite which does web acceptance testing.

Let’s assume we need two Behat suites using the same context depending on `kernel` service and compare the required configuration for every Symfony extension for Behat out there.

#### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Behat/Symfony2Extension

```
# behat.yml
first_suite:
    contexts: - FeatureContext: kernel: '@kernel'            
second_suite:
    contexts: - FeatureContext: kernel: '@kernel'
```

This snippet does not look that bad, mostly because of its size. It gets longer and longer with every new dependency or suite though. Adding a new dependency to a context requires changing the configuration of every suite.

#### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>FriendsOfBehat/SymfonyExtension v1

Finding all places where a context is used and modifying its dependencies is a boring work, which I wanted to avoid in the first version of <abbr title="Friends Of Behat">FOB</abbr>’s SymfonyExtension.

It was possible by registering contexts as services, which fundamentally changes the way you use them in Behat. This is why I decided to write my own extension to integrate Behat with Symfony.

```
# behat.yml
first_suite:
    contexts_services: - FeatureContext            
second_suite:
    contexts_services: - FeatureContext

# Symfony services file loaded by the extension
services:
    FeatureContext:
        arguments:
            - '@ __symfony__.kernel' tags: ['fob.context_service']
```

The duplication is removed, but it has introduced more boilerplate code and own conventions which are hard to get at first:

- ` __symfony__ ` prefix when referencing service from Symfony application container prevented autocomplete in IDEs from working correctly
- `fob.context_service` tag was required by the internal infrastructure to be able to use a service as a context, but it caused bugs quite often as it was so easy to forget it
- `contexts_services` instead of default `contexts` has not introduced any measurable benefit, it was supposed to prevent conflicts with the default configuration if one does not use context services in every suite

The whole process of registering contexts manually was far from pleasant.

#### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>FriendsOfBehat/SymfonyExtension v2

The newest version reduces required boilerplate code by incorporating autowiring and autoconfiguration as first-class mechanisms and sticking to the already known conventions.

```
# behat.yml
first_suite:
    contexts: - App\Tests\Behat\FeatureContext            
second_suite:
    contexts: - App\Tests\Behat\FeatureContext
```

If your context constructor has kernel argument type-hinted, this is all you need to make it work.

No need to worry about duplicated configuration, weird conventions, tagging contexts or using prefixes when referring to services. And you can inject private services as well!

The extension provides a dependency injection configuration which automatically finds your contexts and autowires them:

```
# config/services_test.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\Tests\Behat\:
        resource: '../tests/Behat/*'
```

The following file is loaded only in the test environment. Your production and development environments are not affected with any performance loss. However, if you prefer not to rely on autowiring, you can define the context as a service on your own - remember to make it public though:

```
# config/services_test.yaml
services:
    App\Tests\Behat\FeatureContext:
        arguments:
            - '@kernel'
        public: true
```

### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Symfony Flex recipe

If you use [Symfony Flex](https://symfony.com/doc/current/setup/flex.html) to manage your Symfony application, installation and configuration of the extension could not be easier.

All you need to do is to install the extension and agree for installing a contrib recipe for it:

```
composer install friends-of-behat/symfony-extension:^2.0 --dev
composer exec behat
```

In less than a minute you have a working setup of Behat with SymfonyExtension and you are ready to write your features.

If you do not use Symfony Flex, [follow the installation documentation](https://github.com/FriendsOfBehat/SymfonyExtension/blob/master/docs/01_installation.md#installation).

### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Zero-configuration for most Symfony 3 and 4 based apps

Whether you use Symfony 3 or Symfony 4, the new extension got you covered.

It will try to guess the sensible default configuration and in most cases, you will not need to write any more configuration than the following (assuming Flex have not done it for you):

```
# behat.yml
default:
    extensions:
        FriendsOfBehat\SymfonyExtension: ~
```

[Learn more about the default settings](https://github.com/FriendsOfBehat/SymfonyExtension/blob/master/docs/05_configuration_reference.md#configuration-reference).

### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Mink integration

This extension provides a driver for [Mink](http://mink.behat.org/en/latest/) based on BrowserKit called `symfony`.

If you switch from `Behat\Symfony2Extension`, the behaviour stays mostly the same. There is only one minor implementation detail that is worth to know.

`symfony2` driver from the former extension uses the same kernel for both fetching application services and handling requests. This might lead to unexpected results if your code is not idempotent and relies on services state.

Given the following scenario and its implementation:

```
# Please do not write scenarios like this
# It is just for demonstration purposes
Given the service state is set to 42
Then I should get 42 from the API
And I should get 42 from the API

# Context implementation
/**
 * @Given the service state is set to :state
 */
public function serviceStateIs(string $state): void
{
    $this->service->setState($state);
}

/**
 * @Then I should get :state from the API
 */
public function iShouldGetFromAPI(string $state): void
{
    $this->minkSession->visit('/api/');
    assert($state === $this->minkSession->getPage()->getContent());
}

# Symfony controller
public function __invoke(): Response
{
    return new Response($this->service->getState());
}
```

This scenario would fail on the second `Then` step when using `Behat\Symfony2Extension`.

It happens because the kernel is rebooted after each request, so that the first `Then` step one uses the same container that is used in Behat `Given` step before, but the second `Then` step uses a rebooted container which does not share the same state. This might make your tests fragile, eg. when you change assertions order.

When run with `FriendsOfBehat\SymfonyExtension`, the scenario would fail on the first `Then` step because the service state is not shared between Behat and HTTP application.

If you want to learn more, check out [the documentation about differences between Symfony extensions](https://github.com/FriendsOfBehat/SymfonyExtension/blob/master/docs/04_bs2e_differences.md#differences-from-behatsymfony2extension)or [the documenation about Mink integration](https://github.com/FriendsOfBehat/SymfonyExtension/blob/master/docs/03_mink_integration.md#mink-integration).

### <svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>What’s next?

With all those new, shiny features mentioned above, there are still a few ideas in my mind that would be great to see in the future:

- [providing Mink as a service](https://github.com/FriendsOfBehat/SymfonyExtension/issues/61)
- [autowiring and autoconfiguration for steps definitions](https://github.com/FriendsOfBehat/SymfonyExtension/issues/62)
- [exposing BrowserKit API as complementary to Mink API (with Panther support)](https://github.com/FriendsOfBehat/SymfonyExtension/issues/67)

I made a GitHub project which is used as a [roadmap](https://github.com/FriendsOfBehat/SymfonyExtension/projects/1) for this project.