Symfony2 Keep Your Project Guided with Behat and Mink

This is a summary of how to start with a project on BehatBundle with Mink.

// deps
[buzz]
    git=https://github.com/kriswallsmith/Buzz.git
    target=/buzz
 
[SahiClient]
    git=https://github.com/Behat/SahiClient
    target=/behat/sahi
 
[MinkBundle]
    git=https://github.com/Behat/MinkBundle.git
    target=/bundles/Behat/MinkBundle
 
[Mink]
    git=https://github.com/Behat/Mink.git
    target=/behat/mink
 
[BehatBundle]
    git=https://github.com/Behat/BehatBundle.git
    target=/bundles/Behat/BehatBundle
 
[Gherkin]
    git=https://github.com/Behat/Gherkin.git
    target=/behat/Gherkin
 
[Behat]
    git=https://github.com/Behat/Behat.git
    target=/behat/Behat
 
// autoload.php
'Behat\Mink' => __DIR__.'/../vendor/behat/mink/src',
'Behat\MinkBundle' => __DIR__.'/../vendor/bundles',
'Behat\SahiClient' => __DIR__.'/../vendor/behat/sahi/src',
'Behat\BehatBundle' => __DIR__.'/../vendor/bundles',
'Behat\Behat'       => __DIR__.'/../vendor/behat/Behat/src',
'Behat\Gherkin'     => __DIR__.'/../vendor/behat/Gherkin/src',
'Buzz'             => __DIR__.'/../vendor/buzz/lib',
 
// AppKernel.php
$bundles[] = new Behat\MinkBundle\MinkBundle();
$bundles[] = new Behat\BehatBundle\BehatBundle();
 
// config_test.yml
mink:
    base_url:   http://h.local/web/app_dev.php
    sahi:       
        host:     x.local
    show_cmd:   google-chrome %%s
    #default_session: sahi
    default_session: symfony
behat: ~

Notice how we escape the % sign on the show_cmd above.
Notice also that we want to explicitly set the default_session if it is different than symfony only. Or in the case when we want to avoid typing a specific driver session for every scenario:

  @mink:sahi
  Scenario: ....

Or for the default case:

  @mink:symfony
  Scenario: ....

Now we will generate the structure for starting with Behat:

php app/console --env=test behat --init src/Acme/MyDemoBundle
php app/console --env=test behat --init @MyDemoBundle

Both statements above are equivalent, one using the path and the other using the logical name.

# php app/console --env=test behat @MyDemoBundle
No scenarios
No steps
0m0s

Now if you issue the command below you will get the following response:

# php app/console behat @MyDemoBundle --definitions
Given /^(?:|I )am on "(?P<page>[^"]+)"$/                                                     - Opens specified page.
 When /^(?:|I )go to "(?P<page>[^"]+)"$/                                                     - Opens specified page.
 When /^(?:|I )reload the page$/                                                             - Reloads current page.
 When /^(?:|I )move backward one page$/                                                      - Moves backward one page in history.
 When /^(?:|I )move forward one page$/                                                       - Moves forward one page in history
 When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/                                           - Presses button with specified id|name|title|alt|value.
 When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/                                            - Clicks link with specified id|title|alt|text.
 When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/          - Fills in form field with specified id|name|label|value.
 When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/           - Fills in form field with specified id|name|label|value.
 When /^(?:|I )fill in the following:$/                                                      - Fills in form fields with provided table.
 When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/         - Selects option in select field with specified id|name|label|value.
 When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/                                           - Checks checkbox with specified id|name|label|value.
 When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/                                         - Unchecks checkbox with specified id|name|label|value.
 When /^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/             - Attaches file to field with specified id|name|label|value.
 Then /^(?:|I )should be on "(?P<page>[^"]+)"$/                                              - Checks, that current page PATH is equal to specified.
 Then /^the url should match "(?P<pattern>(?:[^"]|\\")*)"$/                                  - Checks, that current page PATH matches regular expression.
 Then /^the response status code should be (?P<code>\d+)$/

Now in order to make an entry point we issue the command to generate the structure of the Features:

php app/console --env=test behat @MyDemoBundle --init

This will create the structure for us and we can add feature files such as:

Features/feature1.feature
Features/FeatureContext.php
 
// open feature1.feature
# Features/pickup.feature
Feature: DemoSpecification1
  In order to fill a form
  As a website user
  I need to be able to persist an entity
 
#  @mink:sahi
  Scenario: Persisting a new entity
    Given I am on "/persist/"
    And Given I am authenticated
    And I press "completeButton"
    #Then I should see "successfully persisted"
    Then show last response

This code above will get me the structure of the function i need to implement on FeatureContext.php where the steps definitions are defined:

     /**
       * @Given /^Given I am authenticated$/
       */
    public function givenIAmAuthenticated()
    {
        $page = $this->getSession()->getPage();
        $username_field = $page->findField('username');
        $password_field = $page->findField('password');
        $button_submit = $page->findButton('');
        $username_field->setValue('myuser');
        $password_field->setValue('mypassword');
        $button_submit->click();
    }

Now if we rerun the command:

// php app/console --env=test behat @MyDemoBundle
 
 successful log here
 
1 scenario (1 passed)
3 steps (3 passed)
0m2.561s

This will be a friend:
http://mink.behat.org/index.html.

The idea is now that specifications done as features will lead development and direct it in a much efficient way. Getting the job done will be provided by how many of this specifications are implemented and how many of them are covered.

Notice also that if you are using sahi, and you can’t get it to work it is because one reason: You don’t have it installed. Ye sahi needs to be downloaded from here.

For me it was this command after I downloaded it to get it installed:

java -jar  ~/Desktop/install_sahi_v35_20110719.jar

And also before running your tests issue:

~/sahi/userdata/bin$ ./start_sahi.sh

Provided you have installed sahi at your home directory.

Now happy coding! and dont’ forget to help me help you more with the link on the sidebar.

I was having problems with this error when using sahi:
500 page for too many parameters
Warning: session_start(): The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9

But they were resolved by an issue on github.
https://github.com/symfony/symfony/issues/1759#issuecomment-1625472

And in addition I was able to sort out the assets issue hard coding a line like this for feeding the assets:

<link rel="stylesheet" href="http://mydomain.local/web/css/style.css" />

Now we run the same command:

php app/console behat --env=test @MyDemoBundle/MyDemo.feature:13 --no-paths

This is for the bundle MyDemoBundle and for MyDemo feature file and the scenario starting at line 13. There are tags and other niceties which you can look up on the documentation.

Thanks Stof for helping me getting through the rough unknown places.

Do Unit Tests should call real DIC or Fixtures?

Recently I wrote a reply to this post in which interesting classes for *unit* tests were presented. However I made some annotations as to the effect that I always learned from the top guys about testing:

hi there, I like the part that you talked about the Continuous Integration server application. However, i don’t think your tests are unit tests. The reason is that your tests should tend to be isolated into the logic of the test and not in loading interactions with other objects or the database. So to load specific data makes it sound more like functional testing. Specially when you load the DIC, rather I think objects and their methods should be mocked and stubbed. This of course is harder but it is the right way. What do you think?

Idea: Now if i am right it would be neat to create a class that can automatically perhaps implement the mocking or stubbing through the DIC.

http://blog.sznapka.pl/fully-isolated-tests-in-symfony2/comment-page-1/#comment-287