This question assumes some knowledge of Laravel, Behat, and Mink.
With that in mind, I am having trouble making a simple call to the DB from within my Behat FeatureContext file which looks somewhat like this...
<?php
use App\Models\Db\User;
use Behat\Behat\Context\Context;
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 {
public function __construct() {}
/**
* #Given I am authenticated with :email and :password
*/
public function iAmAuthenticatedWith($email, $password) {
User::where('email', $email)->firstOrFail();
$this->visitPath('/login');
$this->fillField('email', $email);
$this->fillField('password', $password);
$this->pressButton('Login');
}
}
When this scenario runs I get this error...
Fatal error: Call to a member function connection() on null (Behat\Testwork\Call\Exception\FatalThrowableError)
Which is caused by this line...
User::where('email', $email)->firstOrFail();
How do I use Laravel Eloquent (make DB calls) from within a Behat/Mink FeatureContext? Do I need to expose something within the constructor of my FeatureContext? Update/add a line within composer.json or behat.yml file?
If there is more than one way to solve this problem and it is worth mentioning, please do.
Additional Details
Laravel: 5.5.*
Behat: ^3.3
Mink Extension: ^2.2
Mink Selenium 2 Driver: ^1.3
Behat Config
default:
extensions:
Behat\MinkExtension\ServiceContainer\MinkExtension:
base_url: "" #omitted
default_session: selenium2
selenium2:
browser: chrome
Laravel need to setup the eloquent and the connection for this to work.
The easy way is to extend laravel TestCase and in the __constructor() call parent::setUp();
It will setup your test environment, like it does when you run php test units in Laravel:
/**
* Setup the test environment.
*
* #return void
*/
protected function setUp()
{
if (! $this->app) {
$this->refreshApplication();
}
$this->setUpTraits();
foreach ($this->afterApplicationCreatedCallbacks as $callback) {
call_user_func($callback);
}
Facade::clearResolvedInstances();
Model::setEventDispatcher($this->app['events']);
$this->setUpHasRun = true;
}
The refreshApplication() will call the createApplication() and it does bootstrap the Laravel and create the $this->app object.
/**
* Refresh the application instance.
*
* #return void
*/
protected function refreshApplication()
{
$this->app = $this->createApplication();
}
Related
I am trying to implement a SimpleCache concrete instance in one of my service classes to also allow caching, however I am having some issues at wiring the dependencies.
config/services.yml
services:
Psr\SimpleCache\CacheInterface: '#Symfony\Component\Cache\Simple\FilesystemCache'
src/Services/Shouter/Sources/Twitter.php
<?php
namespace App\Services\Shouter\Sources;
use Psr\SimpleCache\CacheInterface;
class Twitter
{
/**
* Cache instance
* #var Psr\SimpleCache\CacheInterface
*/
protected $cache;
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
}
This is the error that I get:
You have requested a non-existent service "Symfony\Component\Cache\Simple\FilesystemCache".
Fixed by adding Symfony\Component\Cache\Simple\FilesystemCache: in the services.yaml.
I've bound an interface to its implementation, like this:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\Mail\Contracts\Webhook;
use App\Services\Mail\Clients\MailgunWebhook;
class MailServiceProvider extends ServiceProvider {
protected $defer = true;
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot() {
//
}
/**
* Register the application services.
*
* #return void
*/
public function register() {
$this->app->bind(
Webhook::class,
MailgunWebhook::class
);
}
}
I ran all of these:
php artisan config:clear
php artisan clear-compiled
php artisan optimize
composer dumpautoload
Yet, I still got the "Target is not instantiable error", when trying to use the binding.
After I commented out the $defer property, the binding started to work.
Why can't I use $defer in this case?
As stated in the documentation, to which I linked myself and failed to read it in full, $defer needs the provides() method.
In my case, all I needed to add in my service provider class is this:
public function provides() {
return array(Webhook::class);
}
Thanks to James Fenwick for his comment.
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 have a problem running the basic test that ships with Laravel:
app/tests/Feature/ExampleTest.php
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testBasicTest()
{
$response = $this->get('/');
$response->assertStatus(200);
$this->assertTrue(true);
}
}
When I run it I get the exception NotFoundHttpException. I can access my website in the browser without problems. This problem appears to apply to all my routes.
Using Laravel 5.4
The route / is defined in app/routes/web.php.
It seems that in Laravel 5.4 $baseUrl property was removed from TestCase class.
You may add some setUp to your ExampleTest:
function setUp()
{
parent::setUp();
config(['app.url' => 'https://myserver']);
}
Hope this helps!
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