Skip to content

Instantly share code, notes, and snippets.

@drymek
Created July 4, 2014 08:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drymek/9539dc04b44adb810471 to your computer and use it in GitHub Desktop.
Save drymek/9539dc04b44adb810471 to your computer and use it in GitHub Desktop.
Behat3 Contexts

Cześć,

mam w Behat3 taki scenariusz:

  Scenario: Get task
    Given Entity "task" exists:
      | property    | value                       |
      | title       | Hello                       |
      | description | Lorem ipsum dolor sit amet  |
    When I send a GET request with placeholder to "/api/task/{{task.id}}.json"
    And the JSON should match pattern:
    """
    {
      "title: "Hello",
      "description: "Lorem ipsum dolor sit amet",
      "id": "%integer" 
    }
    """

Tak naprawdę każdy z tych stepów jest w innym Context.

Entity :entityName exists w DataContext

I send a :method request w ApiContext

the JSON should match pattern w MatcherContext

W DataContext mam coś w stylu:

$this->entities[$entityName] = new $entityName();

W MatcherContext mam coś w stylu:

$current = $this->getSession()->getPage()->getContent(); // (możliwe bo ApiContext korzysta z RawMinkContext)
$matcher->match($current, $expected);

W ApiContext chciałbym mieć coś w stylu:

$url = $this->applyPlaceholder($url);
$this->sendRequest($url);

Problem w tym, że applyPlaceholder jest zależne od tego co do tej bazy już dodałem ($this->entities z DataContext).

Rozwiązanie 1 jakie pierwsze mi przyszło do głowy, to: DataContext zamienić na FeatureContext, I w nim użyć Step Execution Chaining:

return new When("I send request a ....");

Ale w Behat3 nie ma już Chainingu w Core, ChainedStepsExtension wydaje się nie działać (chyba, że ktoś ma inne dane).

Rozwiązanie 2: W DataContext i ApiContext użyć traita Behat\Symfony2Extension\Context\KernelDictionary; W DataContext ->setParameter('entity_holder', $this->entities); W ApiContext ->getParameter('entity_holder');

Tak, to wiąże mi Behata z Symfony2Extension na stałe. Byłbym w stanie to jeszcze jakość znieść, bo *.features, i tak są w Symfony, i tak korzystają gdzieś tam z tego Extension. Ale container jest już zamrożony:

Impossible to call set() on a frozen ParameterBag. (Symfony\Component\DependencyInjection\Exception\LogicException)

Rozwiązanie 3: W tej chwili ApiContext dziedziczy, rozszerza Sanpi\Behatch\Context\RestContext (udostępnia wysyłanie tego requesta, rozszerzam go o możliwość podania placeholdera). Więc mógłbym po tym dziedziczyć w DataContext, a ApiContext po DataContext, ale to rozwiązanie które wydaje mi się najbrzydsze z możliwych.

W sumie problem powinien być dość popularny, a jednak nie potrafię znaleźć dobrego rozwiązania. Czy ktoś miałby pomysł jak można rozwiązać ten problem?

PS. Nawet jeśli wydaje Ci się, że masz głupi pomysł, to podrzuć - być może będzie on lepszy niż pozostałe.

@kibao
Copy link

kibao commented Jul 4, 2014

Najlepszym rozwiązaniem byłoby podpiąć się pod event @BeforeStep i w nim zmodyfikować wartości w StepNode, niestety jest to niemożliwe gdyż StepNode nie ma setterów.

Rozwiązanie 1

Stworzenie ParameterBag i wstrzykiwanie go do klas Context które implementują Twój ParameterBagContextAware.
Spójrz na Behat/WebApiExtension tam jest to zrealizowane (klasy Extension, Initializer).

<?php

class DataContext implements ParameterStorageContextAware
{
    public function setParameterStorage(ParameterBag $parameterStorage){
        $this->parameterStorage = $parameterStorage;
    }
    /**
     * Usuń niepotrzebne parametry po scenariuszu
     * @AfterScenario 
     */
    public function clearParameters(){
        $this->parameterStorage->remove('task.id');
    }
    /**
     * @Given Entity :entityName exists 
     */
    public function entityExists($entityName, ...)
    {
        // your logic, set $id

        $this->parameterStorage->set('task.id', $id);
    }
}

class ApiContext implements ParameterStorageContextAware
{
    public function setParameterStorage(ParameterBag $parameterStorage){
        $this->parameterStorage = $parameterStorage;
    }

    /**
     * @When I send a :method request with placeholder to :url
     */
    public function send($method, $url)
    {
        $this->setPlaceholder('{{task.id}}', $this->parameterStorage->get('task.id'));

        $url = $this->applyPlaceholder($url);
        $this->sendRequest($url);
    }
}

Rozwiązanie 2

Stworzenie Extension wstrzykującego nowego EventDistpatcher, który będzie przede wszystkim niezależny od dispatchera z Behata (który to jest lekko zmodyfikowany).
I używanie eventów do celów aktualizacji np. Twoich url.

<?php

class DataContext implements EventDispatcherContextAware
{
    public function setEventDispatcher(EventDispatcher $dispatcher){
        $this->dispatcher = $dispatcher;
        $this->dispatcher->addListener('api.before_apply_placeholder', array($this, 'beforeApplyPlaceholder'));
    }

    public function beforeApplyPlaceholder(PlaceholdersEvent $event){
        if(null !== $this->lastTaskId) {
            $event->addPlaceholder('task.id', $taskId);
        }
    }

    /**
     * @AfterScenario 
     */
    public function afterScenario(){
        $this->lastTaskId = null;
    }
    /**
     * @Given Entity :entityName exists 
     */
    public function entityExists($entityName, ...)
    {
        // your logic, set $id

        $this->lastTaskId = $id;
    }
}



class ApiContext implements EventDispatcherContextAware
{
    public function setEventDispatcher(EventDispatcher $dispatcher){
        $this->dispatcher = $dispatcher;
    }

    /**
     * @When I send a :method request with placeholder to :url
     */
    public function send($method, $url)
    {
        $event = new PlaceholdersEvent();
        $this->dispatcher->dispatch('api.before_apply_placeholder', $event);
        $this->updatePlaceholders($event->getPlaceholders());

        $url = $this->applyPlaceholder($url);
        $this->sendRequest($url);
    }
}

Oczywiście placeholdery w ApiContext powinny być aktualizowane tylko w ramach danego Scenariusza, więc musisz sam to sobie rozwiązać.
PS. Możesz też zrobić eventy zrobić na odwrót, że to ApiContext będzie listenerem.

@drymek
Copy link
Author

drymek commented Jul 14, 2014

Finalnie wybrałem Rozwiązanie 1 zamykając je w Extension. Pierwsza wersja (jeszcze mocno niedopracowana) została udostępniona na GitHubie: ParameterBagExtension

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment