I am trying to write some Behat tests for an application and I need to separate the Contexts so I can use some core elements in different other contexts for example the logged in user step.
behat.yml
suites:
common:
type: symfony_bundle
bundle: CommonBundle
mink_session: symfony2
mink_javascript_session: selenium2
autoload:
'CommonContext': %paths.base%/src/xxx/CommonBundle/Features/Context
contexts: [ xxx\CommonBundle\Features\Context\CoreContext ]
user:
type: symfony_bundle
bundle: UserBundle
mink_session: symfony2
mink_javascript_session: selenium2
autoload:
'UserContext': %paths.base%/src/xxx/UserBundle/Features/Context
contexts:
- Behat\MinkExtension\Context\MinkContext
- xxx\CommonBundle\Features\Context\CoreContext
- xxx\UserBundle\Features\Context\UserContext
DefaultContext.php
namespace XXX\CommonBundle\Features\Context;
use ...
/**
* Class DefaultContext
*/
class DefaultContext extends MinkContext implements Context, KernelAwareContext
{
/**
* #param AbstractEntity $oEntity
*
* #return object
*/
protected function getRepository(AbstractEntity $oEntity)
{
return $this->getService($oEntity);
}
/**
* #return mixed
*/
protected function getEntityManager()
{
return $this->getService('doctrine')->getManager();
}
/**
* #param $id
*
* #return object
*/
protected function getService($id)
{
return $this->getContainer()->get($id);
}
CoreContext.php
namespace XXX\CommonBundle\Features\Context;
use ...
/**
* Class SubContext
*/
class CoreContext extends DefaultContext implements Context, SnippetAcceptingContext
{
/**
* #Given I visit the homepage
* #When I visit the homepage
*/
public function iVisitHomepage()
{
$this->visitPath('/');
}
/**
* #And /^I am logged in as "([^"]*)"$/
* #Then /^I am logged in as "([^"]*)"$/
*/
public function iAmLoggedInAs($username)
{
$user = $this->getContainer()->get('fos_user.user_manager')->findUserByUsername($username);
$this->visit('/login');
$this->fillField('_username', $user->getEmailCanonical());
$this->fillField('_password', self::USER_PASSWORD);
$this->pressButton('_submit');
}
UserContext.php
namespace xxx\UserBundle\Features\Context;
use ...
/**
* Defines application features from the specific context.
*/
class UserContext extends CoreContext implements Context, SnippetAcceptingContext
{
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
}
}
register_user.feature
Feature: Register user
#javascript
Scenario: Register user
Given I am on homepage
And I go to "/register/"
And I am logged in as "foo#bar.com"
Then I should see "Terms and Conditions"
So when I run the test I get an error saying this:
Given I am on homepage
Step "I visit the homepage" is already defined in xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
And I go to "/register/"
Step "I visit the homepage" is already defined in xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
Do i understood this completely wrong or I am missing some settings here?
Do not extend contexts that provide step definitions. There's no way around this.
Good extensions provide contexts with handy methods but no step definitions. In case of the Mink extension, next to the MinkContext you also have RawMinkContext. First provides step definitions and should not be extended. The other one provides helper methods you might be interested in. The RawMinkContext is the one you should extend. Alternatively you can also use the MinkAwareTrait.
There has been a shift on what Contexts where in Behat2 and what they are now in Behat3. Contexts in Behat3 are more about "situations" in which your tests might occur. For instance:
Logged in
Anonymous visitor
Public API call
Authenticated API call
Disabled javascript browser
Enabled javascript browser
And so on.
So I'm afraid that the problem is in your configuration of the CoreContext being used in two suites. You can try to avoid this by running your suites separately so it does not load both configurations (and it does not autoload twice the same Context) or to change the Context strategy to not use the autoload, but something different like encapsulating common step logics into their own classes that are then being used from different Contexts.
Hope it helps
Related
In my app I have a service called "LogService" to log events and other items. I basically need to use this on every controller to log events by users. Instead of having to instantiate this service in each controller, I had two thoughts for accomplishing this.
Option 1: Bind the service into the IoC and then resolve it that way
Option 2: Make a master class with the service in it and then extend it for other classes so they come with the service already bound
I have questions for each of these methods:
Option 1: Is this even possible? If so, would it just be with "App::make()" that it would be called? That way doesn't seem to play too well with IDE's
Option 2: I have done this kind of thing in the past but PHPStorm does not seem to recognize the service from the parent object because it is instantiated by "App::make()" and not through the regular dependency injection.
What would be the best course of action?
Thanks!
You can have it both ways, I think the neatest way would be:
1) Have an interface that describes your class, let's call it LogServiceInterface
2) Create a Service Provider that instantiates your class, like so:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class LoggerServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind(LogServiceInterface::class, function($app)
{
return new LogService();
});
}
}
3) Register this service provider in config/app.ph file:
'providers' => [
// Other Service Providers
App\Providers\LoggerServiceProvider::class,
],
4) Now, in controller you can request the instance of something that implements LoggerServiceInterface straight in the constructor:
(Some controller):
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Repositories\OrderRepository;
class OrdersController extends Controller {
/**
* The logger service.
* #var LoggerServiceInterface $loggerService
*/
protected $loggerService;
/**
* Create a controller instance.
*
* #param OrderRepository $orders
* #return void
*/
public function __construct(LoggerServiceInterface $loggerService)
{
$this->loggerService = $loggerService;
}
/**
* Show all of the orders.
*
* #return Response
*/
public function index()
{
// $this->loggerService will be an instance of your LoggerService class that
// is instantiated in your service provider
}
}
This way, you have got an easy way to quickly change the implementation of your service, moreover, Phpstorm can handle this very easily.
You will still be able to use app()->make() to obtain an instance of your service.
This, however, will not be automatically picked up by Phpstorm. But you can help it to understand that, all you need to do is to use #var annotation, see:
/**
* #var LoggerServiceInterface $logger
*/
$logger = app()->make(LoggerServiceInterface::class);
That way, Phpstorm will know what to expect from that $logger object.
I'd like to be able to use PhpStorm's "Go To Declaration" feature (Command + B on a Mac) in Gherkin feature files when using Codeception. However, PhpStorm doesn't seem to figure out where the steps are defined, and outputs this warning:
Undefined step reference: […]
When I'm using Behat, PhpStorm understands where the steps are defined.
Steps to reproduce
mkdir codeception
cd codeception
composer require "codeception/codeception" --dev
./vendor/bin/codecept bootstrap
./vendor/bin/codecept generate:feature acceptance first
Open the project directory in PhpStorm.
Make sure that PhpStorm knows that Codeception is installed:
Make sure that the PhpStorm plugins Gherkin and Codeception Framework are installed.
Add a step to tests/acceptance/first.feature.
./vendor/bin/codecept gherkin:snippets acceptance
This results in the following code. (Not everything is included – let me know if I need to add anything.)
tests/acceptance/first.feature:
Feature: first
In order to ...
As a ...
I need to ...
Scenario: try first
When I visit "/"
tests/_support/AcceptanceTester.php:
<?php
/**
* Inherited Methods
* #method void wantToTest($text)
* #method void wantTo($text)
* #method void execute($callable)
* #method void expectTo($prediction)
* #method void expect($prediction)
* #method void amGoingTo($argumentation)
* #method void am($role)
* #method void lookForwardTo($achieveValue)
* #method void comment($description)
* #method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
*
* #SuppressWarnings(PHPMD)
*/
class AcceptanceTester extends \Codeception\Actor
{
use _generated\AcceptanceTesterActions;
/**
* Define custom actions here
*/
/**
* #When I visit :arg1
*/
public function iVisit($arg1)
{
throw new \Codeception\Exception\Incomplete("Step `I visit :arg1` is not defined");
}
}
However, PhpStorm doesn't know where iVisit() is. How can I fix this?
Currently PhpStorm seems to use the Behat Context interface to determine which classes define implementations for the Gherkin steps in .feature files, so a workaround to have PhpStorm find the steps in the codeception tester is to add the Behat\Behat\Context\Context interface somewhere in your source tree
/* Context.php */
namespace Behat\Behat\Context;
interface Context { }
and then have the AcceptanceTester implement that interface (which is an empty marker interface)
class AcceptanceTester extends \Codeception\Actor implements Context ...
Not supported yet, please vote:
https://youtrack.jetbrains.com/issue/WI-34963
Building off Roverwolf's answer.
Add this to the top of the AcceptanceTester file.
namespace Behat\Behat\Context {
interface Context { }
}
Then have AcceptanceTester implement that. Wrapping namespaces like this is a common trick in PHP testing to fake methods that exist in other namespaces.
namespace {
class AcceptanceTester extends \Codeception\Actor implements \Behat\Behat\Context\Context
}
}
I have another step definition file, and I use Behat's Context. Then implements Context in the class declaration.
use Behat\Behat\Context;
class myClassSteps implements Context\Context
{
step definitions
}
It works for me.
I set up a simple test scenario to learn behat, but I'm running into some problems. I'm following THIS tutorial.
Here is my feature show:
Feature: show
This is a behat feature to test the article pages.
##TODO
Scenario: I want to view a detailed article page
Given I am logged in
And I'm on "/articles"
When I press an article Image
Then I should see a title
And I should see an Image
And I should see some text
and here is my FeatureContext.php file
<?php
use Behat\MinkExtension\Context\MinkContext;
/**
* Features context.
*/
class FeatureContext extends MinkContext
{
/**
* Initializes context.
* Every scenario gets its own context object.
*/
public function __construct()
{
}
/**
* #Given /^I am on "([^"]*)"$/
*/
public function iAmOn($arg1)
{
throw new PendingException();
}
/**
* #Given /^I press "([^"]*)"$/
*/
public function iPress($arg1)
{
throw new PendingException();
}
/**
* #When /^I fill in "([^"]*)" with "([^"]*)"$/
*/
public function iFillInWith($arg1, $arg2)
{
throw new PendingException();
}
/**
* #Then /^I should see "([^"]*)" in the "([^"]*)" element$/
*/
public function iShouldSeeInTheElement($arg1, $arg2)
{
throw new PendingException();
}
}
However everytime I try to run the feature I get the same result, which looks like this:
Feature: show
This is a behat feature to test the article pages.
Scenario: I want to view a detailed article page # features\show.feature:5
Given I am logged in
And I'm on "/articles"
When I press an article Image
Then I should see a title
And I should see an Image
And I should see some text
1 scenario (1 undefined)
6 steps (6 undefined)
0m0.32s (4.78Mb)
I'm not sure what's causing this problem. I've been looking for a solution but I can't find it. I hope one of you can help me out!
thanks in advance
Your steps do not match your step definitions.
You can let Behat create stubs of your step definitions by implementing SnippetAcceptingContext in your FeatureContext and running Behat with the --append-snippets argument as described here:
http://behat.org/en/latest/quick_start.html#defining-steps
I'm doing my firsts acceptance tests for a Laravel 5 application using Behat 3 and Mink.
The application runs under a Homestead VM.
The test is straightforward and is located in the features/example.feature file. This is the test:
Feature: Sample
In order to learn Behat
As a programmer
I need a simple url testing
Scenario: Registration
Given I am not logged in
When I go to the registration form
Then I will be automatically logged in
The FeatureContext.php has this class:
<?php
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;
use Behat\MinkExtension\Context\MinkContext;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext
{
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
}
/**
* #Given I am not logged in
*/
public function iAmNotLoggedIn()
{
Auth::guest();
}
/**
* #When I go to the registration form
*/
public function iGoToTheRegistrationForm()
{
$this->visit(url('my/url'));
}
/**
* #Then I will be automatically logged in
*/
public function iWillBeAutomaticallyLoggedIn()
{
Auth::check();
}
}
Then, when I run behat from the command line, I expect the test to fail because there is no my/url route (the routes.php file only has a route for /).
However, the test returns green, and this is what I see:
Feature: Sample
In order to learn Behat
As a programmer
I need a simple url testing
Scenario: Registration # features/example.feature:7
Given I am not logged in # FeatureContext::iAmNotLoggedIn()
When I go to the registration form # FeatureContext::iGoToTheRegistrationForm()
Then I will be automatically logged in # FeatureContext::iWillBeAutomaticallyLoggedIn()
1 scenario (1 passed)
3 steps (3 passed)
0m0.45s (22.82Mb)
Of course, I'm using the laracasts/behat-laravel-extension package and this is the content of the beat.yml file:
default:
extensions:
Laracasts\Behat: ~
Behat\MinkExtension:
default_session: laravel
laravel: ~
Thank you very much for any help!
Behat is very simple. It treats a step as failed if an exception is thrown while executing it. It treats a step as successful otherwise.
As far as I can tell from the docs, Auth::check() does not throw an exception if user is not authenticated. It simply returns a boolean value.
Your step should be rather implemented more like the following:
/**
* #Then I will be automatically logged in
*/
public function iWillBeAutomaticallyLoggedIn()
{
if (!Auth::check()) {
throw new \LogicException('User was not logged in');
}
}
Your "I go to the registration form" step succeeds since you don't really verify if the page you visited is the one you expected to load. Again, you should throw an exception if a page you visited is not the right one.
I'm looking for a way to make PHPStorm hide some methods from code completion. I've tried to annotate the DocBlocks with #access private but that does not hide them from view.
Is there any way to hide private API, short of writing/generating a stub file with a limited interface and referencing that in my project?
for example:
Lets say the library has this in it:
<?php
interface IDoABunchOfStuff
{
/**
* My library users use this
*/
public function doFoo();
/**
* My Library needs this but requires that my users don't see it.
*
* #access private
* #visibility none
* #package mylib
* #internal
*/
public function doBar();
}
class Foo extends Something implements IDoABunchOfStuff
{
/**
* does foo
*/
public function doFoo()
{
// ...
}
/**
* does bar. for internal use only
*
* #access private
* #visibility none
* #package mylib
* #internal
*/
public function _doBar()
{
// ...
}
}
And my library user is typing:
<?php
myAwesomeFunction(IDoABunchOfStuff $aFoo)
{
if($->$oFoo->[CTRL+SPACE] // invoking code completion...
Is it possible to (and if it is how do I) make it so that my user never sees _doBar?
Neither of the different annotations i've tried seem to have the desired effect.
P.S. I'm using PHPStorm 4.0.3
additional:
In this case I am implementing ArrayAccess and I don't want offsetGet, offsetSet, offsetExists and offsetUnset cluttering up my code completion window but I've had similar problems elsewhere enough to warrant asking a more generalized question.
Nope -- you cannot do such thing in current version of PhpStorm.
There is a ticket on Issue Tracker that suggests using #access tag for this purpose, but currently it is not scheduled to be implemented for any particular version: http://youtrack.jetbrains.com/issue/WI-5788
Feel free to vote/comment/etc and maybe it will be implemented sooner.