gorghoa/scenariostate-behat-extension

Scenario shared state extension for Behat

Installs: 299 449

Dependents: 1

Suggesters: 0

Security: 0

Stars: 30

Watchers: 3

Forks: 6

Open Issues: 4

Type:behat-extension

v1.0.7 2020-10-07 08:21 UTC

README

Build Status Scrutinizer Code Quality Code Coverage

When to use

Behat scenarios are all about state. First you put the system under test to a special state through the Given steps. Then you continue to manipulate your system through When steps and finally testing the resulting state via the Then steps.

When testing a system like a single page app or a stateful website, the resulting state of our steps is handled by the system itself (either by the browser, or by the php session, etc.).

But, when you are testing a stateless system, chiefly an API, then the resulting state of our steps is handled by no one. This is the case for this extension.

Installation

composer require --dev gorghoa/scenariostate-behat-extension @RC

Then update your project's behat.yml config file by loading the extension:

default:
    extensions:
        Gorghoa\ScenarioStateBehatExtension\ServiceContainer\ScenarioStateExtension: ~

Usage

This behat extension will allow scenarios steps to provide and consume what I call "fragments" of the resulting state.

Each scenario get it's own isolated and unique state.

Let's say a feature like this:

    Feature: Monkey gathering bananas

        Scenario: Monkey gives a banana to another monkey
            When bonobo takes a banana
            And bonobo gives this banana to "gorilla"

See the "this banana"? What we want during the second step execution is a reference to the exact banana the bonobo initially took. This behat extension will help us to propagate the banana refence amongst steps.

Provide state fragment

To share a piece of state with all other scenario's steps, your contexts need to implement the Gorghoa\ScenarioStateBehatExtension\Context\ScenarioStateAwareContext interface.

This interface declares one method to implement: public function setScenarioState(ScenarioStateInterface $scenarioState) which can be imported using ScenarioStateAwareTrait. This ScenarioState is responsible for storing your state.

use Gorghoa\ScenarioStateBehatExtension\Context\ScenarioStateAwareContext;
use Gorghoa\ScenarioStateBehatExtension\Context\ScenarioStateAwareTrait;
use Gorghoa\ScenarioStateBehatExtension\ScenarioStateInterface;

class FeatureContext implements ScenarioStateAwareContext
{
    use ScenarioStateAwareTrait;
}

Then you can publish state fragment through the ScenarioStateInterface::provideStateFragment(string $key, mixed $value) method.

/**
 * @When bonobo takes a banana
 */
public function takeBanana()
{
    $banana = 'Yammy Banana';
    $bonobo = new Bonobo('Gerard');

    // Here, the banana `Yammy Banana` is shared amongst steps through the key "scenarioBanana"
    $this->scenarioState->provideStateFragment('scenarioBanana', $banana);

    // Here, the bonobo Gerard is shared amongst steps through the key "scenarioBonobo"
    $this->scenarioState->provideStateFragment('scenarioBonobo', $bonobo);
}

Consuming state fragments

To consume state fragments provided to the scenario's state, you must add needed arguments to step's methods using ScenarioStateArgument annotation. It can be used easily:

  • inject argument from store with the exact same name: @ScenarioStateArgument("scenarioBanana") or @ScenarioStateArgument(name="scenarioBanana")
  • inject argument from store changing its name: @ScenarioStateArgument(name="scenarioBanana", argument="banana")
use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument;

/**
 * @When bonobo gives this banana to :monkey
 *
 * @ScenarioStateArgument("scenarioBanana")
 * @ScenarioStateArgument(name="scenarioBonobo", argument="bonobo")
 *
 * @param string $monkey
 * @param string $scenarioBanana
 * @param Bonobo $bonobo
 */
public function giveBananaToGorilla($monkey, $scenarioBanana, Bonobo $bonobo)
{
    // (note that PHPUnit is here only given as an example, feel free to use any asserter you want)
    \PHPUnit_Framework_Assert::assertEquals($monkey, 'gorilla');
    \PHPUnit_Framework_Assert::assertEquals($scenarioBanana, 'Yammy Banana');
    \PHPUnit_Framework_Assert::assertEquals($bonobo->getName(), 'Gerard');
}

Using state fragments in Behat hook methods

It's also possible to consume state fragments in hook methods: BeforeScenario & AfterScenario. And much better, the order is not important, you can set your arguments in any order you want:

use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument;

/**
 * @BeforeScenario
 *
 * @ScenarioStateArgument("scenarioBanana")
 *
 * @param string              $scenarioBanana
 * @param BeforeScenarioScope $scope
 */
public function checkBananaBeforeScenario($scenarioBanana, BeforeScenarioScope $scope)
{
    // (note that PHPUnit is here only given as an example, feel free to use any asserter you want)
    \PHPUnit_Framework_Assert::assertEquals($scenarioBanana, 'Yammy Banana');
    \PHPUnit_Framework_Assert::assertNotNull($scope);
}

/**
 * @AfterScenario
 *
 * @ScenarioStateArgument("scenarioBanana")
 *
 * @param string             $scenarioBanana
 * @param AfterScenarioScope $scope
 */
public function checkBananaAfterScenario($scenarioBanana, AfterScenarioScope $scope)
{
    // (note that PHPUnit is here only given as an example, feel free to use any asserter you want)
    \PHPUnit_Framework_Assert::assertEquals($scenarioBanana, 'Yammy Banana');
    \PHPUnit_Framework_Assert::assertNotNull($scope);
}

Why injecting state's fragments through method params

  1. Clear dependencies declaration for the step method
  2. Runtime checks by php: fail quickly if the argument is not present or does not match type hint
  3. The less verbose way of consuming shared scenario state