Skip to content

Instantly share code, notes, and snippets.

@rosstuck
Last active May 24, 2017 12:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rosstuck/0b6ba370645f6b2f5cb1 to your computer and use it in GitHub Desktop.
Save rosstuck/0b6ba370645f6b2f5cb1 to your computer and use it in GitHub Desktop.
Behat / ES Helper
Feature: Cookie Jar
Scenario: The last cookie put in the jar is the first eaten
Given I put a raisin cookie in the jar
And I put a chocolate cookie in the jar
When I eat a cookie
Then I should have eaten a chocolate cookie
Scenario: I eat multiple cookies
Given I put a raisin cookie in the jar
And I put a chocolate cookie in the jar
When I eat a cookie
And I eat a cookie
Then I should have eaten a chocolate cookie
Then I should have eaten a raisin cookie
Scenario: Something something BDD
Given I put a raisin cookie in the jar
And I put a chocolate cookie in the jar
When I eat a cookie
And I eat a cookie
Then I should have eaten at least one chocolate cookie
<?php
use Assert\Assertion;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context, SnippetAcceptingContext
{
/**
* @var EventTimeline
*/
private $timeline;
public function __construct()
{
$this->timeline = new EventTimeline(CookieJar::class);
}
/**
* @Given I put a :flavor cookie in the jar
*/
public function iPutARaisinCookieInTheJar($flavor)
{
$this->timeline->given(new CookiePlacedInJar($flavor));
}
/**
* @When I eat a cookie
*/
public function iEatACookie()
{
$this->timeline->whenI()->eatCookie();
}
/**
* @Then I should have eaten a :flavor cookie
*/
public function iShouldHaveEatenACookie($flavor)
{
assertEquals(
new CookieEaten($flavor),
$this->timeline->next()
);
}
/**
* @Then I should have eaten at least one :flavor cookie
*/
public function iShouldHaveEatenAtLeastOneCookie($flavor)
{
assertContains(
new CookieEaten($flavor),
$this->timeline->getNewEvents(),
'', true, false //phpunit, yo
);
}
}
<?php
// Written for a Broadway style agregate
class EventTimeline
{
/**
* @var string
*/
private $aggregateClassName;
/**
* @var object|null
*/
private $aggregate;
/**
* @var object[]
*/
private $past = [];
/**
* @var object[]
*/
private $future = [];
/**
* @var object[]
*/
private $runningEvents = [];
/**
* @param string $aggregateClassName
*/
public function __construct($aggregateClassName)
{
$this->aggregateClassName = $aggregateClassName;
}
public function given($event)
{
$this->past[] = $event;
}
public function whenI()
{
if ($this->aggregate !== null) {
return $this->aggregate;
}
$this->aggregate = $this->aggregateClassName::reconstituteForHydration();
array_map([$this->aggregate, 'handleEvent'], $this->past);
return $this->aggregate;
}
public function next()
{
$this->captureNewEvents();
$nextEvent = array_shift($this->runningEvents);
if ($nextEvent === null) {
throw new \RuntimeException('No more events');
}
return $nextEvent;
}
public function getNewEvents()
{
$this->captureNewEvents();
return $this->future;
}
private function captureNewEvents()
{
$newEvents = $this->whenI()->getUncommittedEvents();
$this->future = array_merge($this->future, $newEvents);
$this->runningEvents = array_merge($this->runningEvents, $newEvents);
}
<?php
class CookieJar extends AggregateRoot
{
private $cookies = [];
public function restockCookie($flavor)
{
$this->apply(new CookiePlacedInJar($flavor));
}
public function eatCookie()
{
if (empty($this->cookies)) {
throw new \LogicException('Cookies all gone');
}
$this->apply(new CookieEaten(end($this->cookies)));
}
protected function applyCookiePlacedInJar(CookiePlacedInJar $event)
{
$this->cookies[] = $event->getFlavor();
}
protected function applyCookieEaten(CookieEaten $event)
{
if (empty($this->cookies)) {
throw new \LogicException('That...probably wasn\'t a cookie');
}
$key = array_pop($this->cookies);
unset($this->cookies[$key]);
}
}
abstract class AggregateRoot
{
private $pendingEvents = [];
public function handleEvent($event)
{
$classParts = explode('\\', get_class($event));
$methodName = 'apply' . end($classParts);
if (method_exists($this, $methodName)) {
$this->$methodName($event);
}
}
protected function apply($event)
{
$this->handleEvent($event);
$this->pendingEvents[] = $event;
}
public function getUncommittedEvents()
{
$events = $this->pendingEvents;
$this->pendingEvents = [];
return $events;
}
public static function reconstituteForHydration()
{
return new static();
}
}
class CookiePlacedInJar
{
/**
* @var string
*/
private $flavor;
/**
* @param string $flavor
*/
public function __construct($flavor)
{
$this->flavor = $flavor;
}
/**
* @return string
*/
public function getFlavor()
{
return $this->flavor;
}
}
class CookieEaten
{
/**
* @var string
*/
private $flavor;
/**
* @param string $flavor
*/
public function __construct($flavor)
{
$this->flavor = $flavor;
}
/**
* @return string
*/
public function getFlavor()
{
return $this->flavor;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment