PHPSpec Advance

This post is inspired in the development here.

I received great help from jbohn.

So we are starting according to certain scenario.

Feature: Member rents video
  As a video-club member
  I want to rent a video
  So that I can take it away with me and watch it conveniently at home
 
Feature: Members rents video
  As a video-club member
  I want to rent a video
  So that I can take it away with me and watch it conveniently at home
 
  Scenario: Renting single video from oldies section
    Given I am in the "review" page
      And the selected video is "Revolution OS"
     When I click on "Rent"
     Then "£2" is added to my total
 
  Scenario: Renting 3 videos from oldies promotion
    Given I am in the "review" page
      And "Revolution OS" is selected
      And "Blade Runner" is selected
      And "The Wall" is selected
     When I click on "Rent"
     Then "£5" is added to my total

Then we run behat on the file and we are getting:

There is an error with the view already. The reason why we have passed the first step is only because there is already a router for the framework and debugging enabled. However the page that is gotten back by the step is not really a valid page. Once we are at this point we go deeper into the PHPSpec cycle now.

We run the following command because phpspec installed via pear is not the one we are using. Why? Because it does not have knowledge of our updates and also it does not have knowledge of our third party libraries, and also because we want to have full control on what gets loaded. I ran into a redefine issue with function any() conflict between phpunit and hamcrest library.

So here is the command I use:

~/sites-2/FormModelProjectBundle (phpspec) vendor/phpspec/scripts/phpspec.php vendor/bundles/Cordova/Bundle/FormModelBundle/

The `phpspec` script is smart enough to look for the right Spec folder under the bundle and runs the phpspec for IndexSpec.php which is made locatable because of its Spec.php ending.

The resulting output of the previous command is:

E
Exceptions:
 
  1) Cordova\Bundle\FormModelBundle\Specs\views\Index renders the selected video
     Failure\Exception: $this->runExamples($exampleGroup, $reporter);
     InvalidArgumentException: Unable to find template "CordovaFormModelBundle:index.html.twig".
 
Finished in 0.134287 seconds
1 example, 1 exception

Notice here that it tells us that we have the wrong path for our template. Which theoretically is unimplemented. The exact error means that we actually need to insert the logical controller name between the ::. This is familiar to sf2 developers. So we do the change and having a lame default controller we have now the following error:

F
 
Failures:
 
  1) Cordova\Bundle\FormModelBundle\Specs\views\Index renders the selected video
     Failure\Error: $output->should->contain('Revolution OS');
     expected to contain 'Revolution OS', found no match (using contain())
     # ./vendor/bundles/Cordova/Bundle/FormModelBundle/Specs/views/IndexSpec.php:16

Finished in 1.080713 seconds
1 example, 1 failure

Notice: we could have used the generation of a bundle. However since controllers are mostly custom code then there is no point really in using generator unless it is a new action to be placed in a new bundle. So there is no need in sf2 to use a generation command here unless in the case where we are putting things into a new bundle. For all other cases we just copy/paste a controller common template.

Now we know where all our efforts must concentrate, namely here $output->should->contain('Revolution OS');. This means the template rendered has nowhere the label `Revolution OS`. The major problem here is that we need to work on the loading of matchers for Symfony2.

Once we worked on the matcher `Contain` we can run the phpspec command again and we get:

vendor/phpspec/scripts/phpspec.php vendor/bundles/Cordova/Bundle/FormModelBundle/Specs/views/IndexSpec.php
.
 
Finished in 1.029784 seconds
1 example

Now we are ready to head onto developing the C standing for the controller layer. The controller basically is in charge of doing the dispatching of things. So we need to work again on the Symfony2 controller version of things.

<?php
 
class DescribeIndex extends View
{
    function itRendersTheSelectedVideo()
    {
        $video = \Mockery::mock(
            'Application_Model_Video', array('getName' => 'Revolution OS'));
 
        $output = $this->render('MyBundle:Video:detail.html.twig', array(
            'video' => $video,
        );
        $output->should->contain('Revolution OS');
    }
}
<?php
 
use PHPSpec\Context;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
 
class View extends Context
{
    private $engine;
 
    public function setTemplatingEngine(EngineInterface $engine)
    {
        $this->engine = $engine;
    }
 
    public function spec()
    {
        $interceptor = call_user_func_array(
            array(
            '\PHPSpec\Specification\Interceptor\InterceptorFactory',
            'create'), func_get_args()
        );
 
        $interceptor->addMatchers(array('contain', 'haveSelector'));
        return $interceptor;
    }
 
    public function render($template, array $templateContext = array())
    {
        if (is_null($this->engine)) {
            throw new \LogicException('No templating engine set.');
        }
 
        return $this->spec($this->engine->render($template, $templateContext));
    }
 
}

Zombie Pitfalls: Part II

I ran into a problem with connection refused (111) by zombie. And I was told by @havvg to do the following. This is adapted from @havvg gist. You can find his blog here too.

    zombie:
      auto_server: false

and

var net = require('net');
var sys = require('sys');
var zombie = require('zombie');
var browser = null;
var pointers = [];
var buffer = "";
 
net.createServer(function (stream) {
  stream.setEncoding('utf8');
  stream.allowHalfOpen = true;
 
  stream.on('data', function (data) {
    buffer += data;
  });
 
  stream.on('end', function () {
    if (browser == null) {
      browser = new zombie.Browser({ debug: true });
 
      // Clean up old pointers
      pointers = [];
    }
 
    eval(buffer);
    buffer = "";
  });
}).listen(8124, '127.0.0.1');
 
console.log('Zombie.js server running at 127.0.0.1:8124');

This works.

Another problem was that zombie was interacting with modernizr library:

"TypeError: Cannot set property cssText of [object Object] which has only a gettern    at Object.a (/js/libs/modernizr-1.7.min.js:2:2493)n    at Object.flexbox (/js/libs/modernizr-1.7.min.js:2:2595)n    at Object.<anonymous> (/js/libs/modernizr-1.7.min.js:2:7228)n    at Object.<anonymous> (/js/libs/modernizr-1.7.min.js:2:8964)n    in http://h.local/app_test.php/login"

I took away the library and of course it solved the problem.

Display Git Branch On Prompt

Update:

I was told $(__git_ps1 “(%s)”) is better but did not let me customized colors as this one. So it is good.

Last update, this is the end of the matter:

BLACK="\[\033[0;38m\]"
RED="\[\033[0;31m\]"
RED_BOLD="\[\033[01;31m\]"
BLUE="\[\033[01;34m\]"
GREEN="\[\033[0;32m\]"
 
# PS2: shown while waiting for input (interactive)
export PS2="$GREEN\w$BLACK "
# PS1: standard shell prompt
export GIT_PS1_SHOWUPSTREAM="auto verbose"
export GIT_PS1_SHOWDIRTYSTATE="yes"
export GIT_PS1_SHOWSTASHSTATE="yes"
export GIT_PS1_SHOWUNTRACKEDFILES="yes"
export PS1="$GREEN\w$BLACK$RED_BOLD\$(__git_ps1)$BLACK "

Another update:

# PS2: shown while waiting for input (interactive)
export PS2="$GREEN\w$BLACK "
# PS1: standard shell prompt
export GIT_PS1_SHOWUPSTREAM="auto verbose"
export PS1="$GREEN\w$BLACK$RED_BOLD\$(__git_ps1)$BLACK "

Do you want the current branch name displaying on your terminal like this?

Then take this code and insert it at the bottom of your .bashrc and enjoy:

parse_git_branch () {
   git symbolic-ref HEAD 2> /dev/null | sed -e 's/\^0$//' | sed 's#HEAD\ \(.*\)# (\1)#' | sed 's/refs\/head$
}
 
BLACK="\[\033[0;38m\]"
RED="\[\033[0;31m\]"
RED_BOLD="\[\033[01;31m\]"
BLUE="\[\033[01;34m\]"
GREEN="\[\033[0;32m\]"
 
export PS1="$BLACK\u@\h:$GREEN\w $RED_BOLD\$(parse_git_branch)$BLACK \$ "

Adapted from here.

Multi-Contextualization For Behat

The seed for the concept of multi-contextualization was stated in an earlier post here. This idea although perhaps not compatible with the initial intent of Behat is in high use for real life application development.
There was a recent post on stackoverflow about this to which a developer @takeshi answered in a very interesting way:

Use class inheritance and separate contexts.
 
# /features/contexts/
AbstractContext extends BehatContext {}
FeaturenameContext extends AbstractContext {}
Then in /feature/FeatureContext.php import the context files:
 
/**
 * Initializes context.
 * Every scenario gets it's own context object.
 *
 * @param array $parameters context parameters (set up via behat.yml)
 */
public function __construct(array $parameters) {
 
    // import all context classes from context directory, except the abstract one
 
    $filesToSkip = array('AbstractContext.php');
 
    $path = dirname(__FILE__) . '/../contexts/';
    $it = new RecursiveDirectoryIterator($path);
    /** @var $file  SplFileInfo */
    foreach ($it as $file) {
        if (!$file->isDir()) {
           $name = $file->getFilename();
           if (!in_array($name, $filesToSkip)) {
               $class = pathinfo($name, PATHINFO_FILENAME);
               require_once dirname(__FILE__) . '/../context/' . $name;
               $this->useContext($class, new $class($parameters));
           }
        }
    }
}

Here I would like to highlight not the use of inheritance because I prefer sub-contextualization as I had explained in an earlier post, but rather I would like to highlight the alternative mechanism to load a specific context. This is very important as is one earlier effort for multi-contextualization for Behat. I hope more development is done in this branch. If not I think this one is going to be an open opportunity for development in the near future. @TODO