Behat context in a trait - php

The basic Behat use case recommends using FeatureContext class. Also, you can specify any other PHP classes in the features/bootstrap directory and they are loaded, but in the alphabetical order, no matter what the dependencies are.
Given there is a trait and a FeatureContext class:
features/bootstrap/FeatureContext.php
features/bootstrap/MyLovelyTrait.php
What is the best way to load it properly? Obviously, MyLovelyTrait is used within the FeatureContext:
class FeatureContext extends BehatContext {
use MyLovelyTrait;
}
And that fails because M > F, in the alphabet.
I will be happy to use composer autoloading, but I don't want to require_once the autoload.php file in the top of BehatContext.php file. Is there a way to specify this in behat.yml configuration? Also, any other best practice answer regarding class-loading of Behat context files will be appreciated.

I'm not 100% sure this is answering your question but I am under the impression that you are trying to use multiple context files? If so you don't need the use statement instead within the FeatureContext.php construct method we use the line:
$this -> useContext('Subcontext', new Subcontext($parameters));
In this case the other context you want to use is called "Subcontext".

A good reason not to useContext('Subcontext') can be found in the Changelog of the upcoming version 3 of Behat:
3.0.0beta1 / 2013-08-13
...
* Subcontexts removed in favor of context pools

I hacked around it by working with the grain of behat -- all my traits start with "A". Examples:
// FeatureContext.php is at features/bootstrap/FeatureContext.php
<?php
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
class FeatureContext extends BehatContext
{
use AWebDriverContextTrait;
}
and
// AWebDriverContextTrait is at features/bootstrap/AWebDriverContextTrait.php
<?php
trait AWebDriverContextTrait {
/**
* #Given /^I am on "([^"]+)"/
*/
public function iAmOnSite($url)
{
$this->driver = new \Behat\Mink\Driver\Selenium2Driver(
'firefox',
''
);
$this->session = new \Behat\Mink\Session($this->driver);
$this->session->start();
$this->session->visit($url);
}
private $driver;
private $session;
}

Related

PHP's "use" Keyword and Autoloading

My question is in three parts:
Does putting in a use statement trigger the autoloader immediately, or does it wait until the class is used? (lazy-loading)
If autoloading isn't done in a lazy-load fashion, could that negatively affect performance?
Which pattern is best to follow, and why? PhpStorm shows "Unnecessary fully qualified name..." as a code issue when the use statement isn't employed.
Here's an example class definition for a Laravel controller with a use statement:
namespace App\Http\Controllers;
use Carbon\Carbon;
class FooController extends Controller
{
/**
* This action uses the Carbon class
*/
public function bar1()
{
return view('foo.bar1', ['now' => new Carbon()]);
}
/**
* This action does not use the Carbon class
*/
public function bar2()
{
return view('foo.bar2');
}
}
The same class without the use statement:
namespace App\Http\Controllers;
class FooController extends Controller
{
/**
* This action uses the Carbon class
*/
public function bar1()
{
return view('foo.bar1', ['now' => new \Carbon\Carbon()]);
}
/**
* This action does not use the Carbon class
*/
public function bar2()
{
return view('foo.bar2');
}
}
1) The class is autoloaded when you perform a new Class() statement.
2) see 1)
3) Which pattern is best to follow and why?:
I'd recommend to use use because you might get into a situation where you have really long namespaces and your code will become unreadable.
From the php docs:
This example attempts to load the classes MyClass1 and MyClass2 from
the files MyClass1.php and MyClass2.php respectively.
<?php
spl_autoload_register(function ($class_name) {
include $class_name . '.php';
});
$obj = new MyClass1();
$obj2 = new MyClass2();
?>
Namespaces are only an additional feature to organize classes.
EDIT: As #IMSoP pointed out in the comments, new is not the only time the autoloader is triggered. Accessing a class constant, static method, or static property will also trigger it, as will running class_exists.
The use statement can be thought of like a C pre-processing macro, if you're familiar with those: it rewrites the current file at compile time to let you write a short name for a long class, function, or constant name. It doesn't trigger autoloading, as it doesn't care if a class exists or not.
For instance, if you write use Foo\Bar\Baz as X, then everywhere that X is mentioned as a class name, the PHP compiler rewrites that to mention Foo\Bar\Baz instead. Only when code mentioning the class (e.g. new X, X::FOO, X::doSomething()) is actually run does it see if there really is a class Foo\Bar\Baz, and trigger the autoloader as necessary.
The common form use Foo\Bar\Baz is just shorthand for use Foo\Bar\Baz as Baz, assigning the alias Baz to the class name Foo\Bar\Baz.
As the manual points out the alias is only processed at compile time, so dynamic lookups will not use it. In the example above, class_exists('X') will return false, but you can use class_exists(X::class) to expand out the alias - the compiler will automatically substitute the full class name as a string, so at run-time, the expression will be class_exists('\Foo\Bar\Baz').
Whether use statements make your code better is therefore entirely a matter of style: the intent is that your code will be more readable without the long fully-qualified class names, but it will make no difference to how the code actually runs.

Either the step is already declared or Mink instance has not been set on Mink context class

I'm trying to use make some tests around a Drupal project (but Behat is out of it), however I'm having trouble around Mink and its session, and I must admit I have no clue about what I am doing.
Here are my files so far:
FeatureContext.php
use Drupal\DrupalExtension\Context\RawDrupalContext; #not used
use Behat\Mink\Exception\ExpectationException;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Session;
use Behat\MinkExtension\Context\MinkContext;
use Behat\Behat\Context\Context; #not used
use Behat\Mink\Mink;
use DMore\ChromeDriver\ChromeDriver;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends MinkContext implements SnippetAcceptingContext {
protected $mink;
/**
* FeatureContext constructor.
* Initializes context.
* PLEASE NOTE THAT I'M NOT SURE ABOUT THIS, BUT IT SEEMS TO WORK SO FAR
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct() {
$this->mink = new Mink(array(
'browser' => new Session(new ChromeDriver('http://localhost:9222', null, 'http://www.website.rec'))
));
// The rest of my custom functions
}
}
behat.yml
default:
suites:
default:
contexts:
- FeatureContext
- Drupal\DrupalExtension\Context\DrupalContext
- Drupal\DrupalExtension\Context\MinkContext
- Drupal\DrupalExtension\Context\MessageContext
- Drupal\DrupalExtension\Context\DrushContext
extensions:
DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~
Behat\MinkExtension:
browser_name: chrome
base_url: http://www.spheria.rec
sessions:
default:
chrome:
api_url: http://localhost:9222
Drupal\DrupalExtension:
blackbox: ~
test.feature
Feature: Sample feature
Scenario: Arrived on website, checking out what's around me
Given I am an anonymous user
And I go to "/"
And I should see "Se connecter"
And I should see "Nom d'utilisateur"
And I should see "Mot de passe"
When I fill in "admin#spheria.com" for "name"
And I fill in "admin" for "pass"
And I press "Se connecter"
Then I should get a 200 HTTP response
And the url should match "/dashboard"
And I should see "Tableau de bord"
My problem is that if I use MinkContext in the behat file and in FeatureContext, the consoles returns me that every single function have been declared twice (at least, MincContext::PressButton but I wouldn't be surprised if the problem would happen with something else)
When I remove it from both behat.yml and FeatureContext, it doesn't recognize anything, and asks me to define these functions, which makes sense I guess.
And when I use MinkContext in only the behat file or the FeatureContext one, I get an error saying this:
Mink instance has not been set on Mink context class. Have you enabled the Mink Extension? (RuntimeException)
I'm using the DMore Chrome Driver, because I have trouble running Chrome with Selenium properly, and I have the feeling that instancing Mink in the constructor is creating some troubles.
It's an euphemism to say that I'm totally lost on what I should do.
How can I solve this problem?
Thank you in advance
You need to extend MinkContext only once else it will see duplicate steps for each time you extend it.
One of the contexts from behat.yml is already extending MinkContext so you need to:
remove that class and extend it in your FeatureContext
or
your FeatureContext should not extend MinkContext but RawMinkContext

Getting feature file path within BehatContext

I have a FeatureContext class, whose methods implement step definitions for my feature scenarios.
Is it possible to obtain information about current scenario or feature within FeatureContext class? To be specific, I need the path of the feature file, so I can create directory paths relative to dirname('file.feature').
The context class resides somewhere in vendor directory structure, i.e. far away from the actual feature file, so getting the path using __DIR__ would not work.
Any ideas?
Thanks, Sascha
I did find an answer to my question: "hooks"
class MyContext implements Context
{
// ...
/**
* #BeforeScenario
*/
public function beforeScenario(BeforeScenarioScope $scope)
{
var_dump($scope->getFeature()->getFile());
}
}

Behat 3 - Behat\Behat\Context\Step\Given not found

I'm testing Behat/Mink for the first time with a simple example.
When I launch behat I have this error :
PHP Fatal error: Class 'Behat\Behat\Context\Step\Given' not found in /var/www/behat-test/features/bootstrap/FeatureContext.php on line 31
features/bootstrap/FeatureContext.php :
<?php
require_once './vendor/autoload.php';
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\MinkExtension\Context\MinkContext;
use Behat\Behat\Context\Step;
class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext
{
// ......
/**
* #Given I am logged in as :username
*/
public function iAmLoggedInAs($username)
{
return array(
new Step\Given('I go to "login.php"'), // line 31
new Step\When('I fill in "My name" with '.$username),
new Step\When('I press "Login"')
);
}
}
In Behat2, Given/When/Then classes were used for step chaining. Since this technique brought more problems (with maintenance) then benefits, they're no longer supported in Behat3 (which you apparently use). It's also not recommended to follow this practice.
See https://github.com/Behat/Behat/issues/546.

In phpdoc, define class properties in separate file

I am using a framework that allows adding new components to the framework's base class. Is there any way to document these new methods without changing the frameworks files so I can click through to the method in my IDE.
I highly recommend against trying to inject a subclass. If the framework instantiates the class you're extending directly, you'll need to find a way to get it to use your subclass instead.
NetBeans and PhpStorm (and probably many others) will combine elements from multiple definitions of the same class/interface. This allows you to add properties and methods to any existing class without modifying the original source.
/**
* Framework controller base class.
* Provides helpers via __call().
*/
class Framework_Controller { ... }
Now create a file in your code base that you never require containing the same class definition. Your IDE should still parse it and merge its elements with the class above:
if (false) { // Safety first!
/**
* ACME Co. controller base class.
*
* #method ACME_Model_User getUser Load user via authentication helper
*/
class Framework_Controller { /* nothing to add */ }
}
You can try using an interface and declaring the methods normally instead of with #method. That is what we did with Zend_View since it's already an interface. I haven't tried mixing class with interface to see if PhpStorm likes it.
It depends mainly on IDE you are using. I think you should extend base class and add there some new methods/properties with phpdoc comments. Of course changing framework's files is no solution as you already mentioned
You could extend the original class, redefine the function, with new doc, and call the parent function.
e.g.
class newClass extends originalClass
{
/**
* New PHPDoc
*/
function functionName($a)
{
return parent::functionName($a);
}
}
Try to use #see or inline #link directives.

Categories