Behat Laravel 5 Extension
Hey, everyone -
I've been working on making the process of using Behat in Laravel 5 much easier this last week. The first step in that process was to create a special integration layer between Behat and Laravel.
If any of you are interested in this sort of thing, I could use some testers!
https://github.com/laracasts/Behat-Laravel-Extension
I don't have any docs up just yet, but you'll find a link in the readme to a simple example project that should demonstrate everything you need. If you want, clone that repo, do composer install, and then run vendor/bin/behat to trigger the example feature.
A few benefits to using this extension:
- It doesn't depend on anything like Goutte, so it offers a super-fast way to test your UI.
- Laravel is automatically rebooted after each scenario (so nothing like user sessions are persisted).
- Specifying custom environment files (like the .env one) for different app environments was a little tricky in Laravel 5. I talked to Taylor about it, and he added a
$app->loadEnvironmentFrom()method that we can hook into. So the extension does that for you automatically. (By default, it'll look for.env.behat, but you can change this inbehat.yml.) - Instantly have access to Laravel (things like facades and such) from your
FeatureContext.
Anyhow - it's just an alpha extension for now, but seems to be working good.
Must try.
Thank you @JeffreyWay .
@JeffreyWay very interesting! Thanks for putting the time and effort into this. Based on your twitter feed, it seems like you've been on a behat binge lately. Would you say that you've largely moved away from codeception at this point? I'm sure it's already in the works but would love a video on behat and how it differentiates from codeception and others, etc.
@imJohnBon - I like Codeception a lot. It sort of depends upon what your needs are for the app.
Excellent Jeff, Will be adding to my project tomorrow for testing.
I have been pulling my hair out today thinking about how to load different env files on Behat & PhpSpec but could not come up with a way. I like the new Dot env system but for using a local environment and testing on the same server it is issue. I have just surrendered to using the same env for testing and local. Just having to seed my data more often of If want to boot up the site and physically test the UI.
In the past getting behat to use my acceptance db's has been a beast. And as far as I can tell you just fixed that. Thank you sir.
"The first step in that process"....there are more steps incoming...mind blown.
@JeffreyWay Good to know. Well, would love to hear more about what situations would call for one or the other eventually.
@imJohnBon I used to be a codeception all the way guy. But once you get used to using behat for both TDD'ing the domain AND TDD'ing the UI using the same gherkin files (which I take time to write before I code anything)...it's addictive. Use phpspec to TDD the individual domain classes and it's pure gold.
*obligatory everzet blog link: http://everzet.com/post/99045129766/introducing-modelling-by-example
@JeffreyWay, thanks SO much for this! I'm hoping for a video (probably using this extension?) expanding on that tantalizing video you did as part of the Testing Jargon series, showing you using Behat to test both your UI and Domain contexts. I tried to stop the video to see how you set it up with Laravel a few times, haha. Also, are you still planning on updating your Laravel testing book (which was great btw) from phpunit and Codeception to phpspec and behat with Laravel? I know I'm asking a lot, but that would be my dream come true! Thanks again :)
Will be testing.
Obviously I you would like to interact with the database you need a little bit more setup. Add this to your FeatureContext.php file:
/**
* @static
* @beforeSuite
*/
public static function setUpDb()
{
Artisan::call('migrate:install');
}
/**
* @static
* @beforeFeature
*/
public static function prepDb()
{
Artisan::call('migrate:refresh');
Artisan::call('db:seed');
}
Further more I've modified the config/database.php to accept a custom connection and custom database for sqlite:
'default' => env('DB_CONNECTION', 'mysql'),
[...]
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', storage_path().'/database.sqlite'),
'prefix' => '',
],
Then in my .env.behat I have overridden these:
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
@JeffreyWay Thanks for all your work around it. I have tested it a little bit and it looks like that the application is not properly reset after scenario. In my example it seems that the user is being kept to the end of the testing. I’ve compiled some example tests so you can easily verify about what I mean. https://github.com/tmalecki/Behat-Laravel-Extension-Example-App Unless I make some kind of mistake I’m not yet aware of. Thanks
@JefferWay This has worked perfect......
A little bit different to what @FrankPeters has done if you create this trait below.
trait PrepareTestEnvironment
{
/**
* Drop database, Create a fresh one and export it to a fixture
* @BeforeSuite
*/
public static function migrate() {
Artisan::call('migrate');
}
/**
* Load the empty fixture before each Scenario
* @BeforeScenario
*/
public static function beginTransactions() {
DB::beginTransaction();
}
/**
* Load the empty fixture before each Scenario
* @AfterScenario
*/
public static function rollback()
{
DB::rollback();
}
}
Then in your context files just add use PrepareTestEnvironment;
Same before and after filters use in TestDummy :-)
@tmalecki - Can you do composer update and try again?
Also, for your @beforeScenario block, just do:
/**
* @BeforeScenario
*/
public function beforeScenario()
{
Artisan::call('migrate');
Artisan::call('db:seed');
}
@JeffreyWay, thanks, looks good!
Another question: is it allowed to use the kernel object in FeatureContext? For example:
/**
* @BeforeScenario
*/
public function beforeScenario()
{
$this->kernel['Illuminate\Contracts\Console\Kernel']->call('db:seed');
}
The above code works perfectly before first scenario, but before any other scenario I receive:
Target [Illuminate\Contracts\Console\Kernel] is not instantiable. (Illuminate\Container\BindingResolutionException)
Thanks.
@tmalecki - The repo you linked to should work out of the box now. Just pushed a fix to update the property that you were using. One thing is that I changed the $kernel property name to $app. So if you don't want to use the Laravel facades, you would use App, and then you'll have access to $this->app(). Personally, though, I'd just do app('path/to/class').
Also, everyone, I added a similar trait to what @oes referenced. I often use that, so it makes sense to include it out of the box. Just add use DatabaseTransactions to your FeatureContext. (The full import path is Laracasts\Behat\Context\DatabaseTransactions).
I think I am being stupid but I am getting this error "[Behat\Behat\Context\Exception\ContextNotFoundException]
FeatureContext context class not found and can not be used." when I follow @JeffreyWay's instructions.
@mstnorris - Are you pulling in the example project - https://github.com/laracasts/Behat-Laravel-Extension-Example-App - or are you setting up Behat with a new app? If the latter, have you done behat --init yet?
@JeffreyWay thanks, that link worked. All good.
Issue 1 (fixed): For 'Maximum function nesting level of '100' reached, aborting!', adding xdebug.max_nesting_level=500 to /etc/php5/cli/conf.d/20-xdebug.ini inside Homestead, problem fixed.
Issue 2 (fixed): In order to use mink helpers in multiple context classes you need your UI contexts to extend "RawMinkContext" not "MinkContext". Once this is done you can access the functions by $this->getSession(), $this->assertSession(), and some built in helpers like $this->visitPath(). However the original problem still exists. When running your domain non-mink extending classes in tandum with UI tests, you will get an error "Domain\StudentContext::getSession() not found behat-laravel-extension/src/Context/KernelAwareInitializer.php on line 78". Having the last listed domain class extend RawMinkContext/MinkContext fixes this. Problem being mink is not used in this context.
ORIGINAL POST:
I have 2 suites, domain and ui. They look like this:
default:
extensions:
Laracasts\Behat\ServiceContainer\LaravelExtension: ~
Behat\MinkExtension\ServiceContainer\MinkExtension:
default_session: laravel
laravel: ~
suites:
domain:
paths: [ %paths.base%/features/student, %paths.base%/features/teacher]
contexts: [ Features\Bootstrap\Domain\StudentContext, Features\Bootstrap\Domain\TeacherContext]
ui:
paths: [ %paths.base%/features/student, %paths.base%/features/teacher]
contexts: [ Features\Bootstrap\UI\StudentContext, Features\Bootstrap\UI\TeacherContext ]
The domain features don't extend mink as they have no ui code. When I run behat it will complain about the last file in my domain list so in this case "Domain\TeacherContext", saying: call to undefined method TeacherContext::getSession() in behat-laravel-extension/src/Context/KernelAwareInitializer.php on line 78.
I can get around the error by having my domain contexts extend mink (even though they have no use for mink in this context). Is the accepted solution to have your last non-mink file extend mink (weird but it works), or is there a way to load extensions on a suite level?
Anyone has a problem when we do "$this->pressButton('Register');" we got an error "PHP Fatal error: Maximum function nesting level of '100' reached, aborting! in /Users/.../vendor/composer/ClassLoader.php on line 291" ?
@heliocorreia did you read my previous post? The answer is there.
@Klorox you are right, the xdebug.max_nesting_level=500 solves the problem =)
@heliocorreaia Awesome.
There is a weird issue that I am facing. I am trying to implement Behat for my new project. I am following your tutorial but there is one issue.
When I post the form to the same url it is not working. Not sure if the issue is with Behat or Laravel. my route is
Route::any( 'create', 'Controller@method' );//code in my route file
function method() { //method in my controller
if ( \Request::isMethod( 'post' ) ) {
MyItem::create ( \Input::all( ) );
}
return view( 'add' );
}
This is the code that is not working but if I break the routes to two methods and handle post in another method it works. what can be the issue.
Hi everyone, I'm wondering someone has similar problem. I'm trying to test my login functionality but it looks like I have some problem with database transactions.
I'm running my tests inside a transaction (using Laracasts\Behat\Context\DatabaseTransactions trait created by @JeffreyWay) using sqlite database (tried with mysql too), and first line of my scenario is actually creating new user and adding it into the database. When I dd all users from database inside any method for specific scenario, I see that dummy user data is printed in the console, but when my browser is opened (I'm using selenium) and if I dd all users from database inside controller method that is being called, I don't see that dummy user at all. And of course, since that user is not in database, I get message that I'm trying to log in with wrong credentials.
If I do the same thing as above, but I don't use the transactions, everything is working fine and user is persisted into the database.
@Klorox I'm trying to add suites as you wrote but my gulp tdd (using laravel-elixir-behat) doesn't find my new contexts. Any clue why?
I'm using this folder structure

My behat.yml file looks like this
default:
extensions:
Laracasts\Behat:
# env_path: .env.behat
Behat\MinkExtension:
default_session: laravel
laravel: ~
show_cmd: '/Applications/Firefox.app/Contents/MacOS/firefox %s'
suites:
domain:
paths: [ features/authentication]
contexts: [Features\Bootstrap\Domain\AuthenticationContext]
ui:
paths: [ features/authentication]
contexts: [Features\Bootstrap\UI\AuthenticationContext]
the behat output looks like this
o scenarios
No steps
0m0.04s (18.32Mb)
I have features and it works if I remove the suites. But then I can't have a domain and a UI context I guess.
@JeffreyWay et al
I am finding that using this approach with Behat 3 and the Laravel Extension quite flaky.
For example, using the steps outlined here https://github.com/tmalecki/Behat-Laravel-Extension-Example-App work great THE FIRST TIME.
However, if I have to do a composer dump-autoload for some reason (in my case, it is required to get PHPUnit working) after that process has completed, Behat breaks producing the infamous ''Maximum function nesting level of '100' reached'
Scenario: Home Page # features/example.feature:6 exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Maximum function nesting level of '100' reached, aborting!' in /Users/mikee/Documents/Projects/rangladex5/vendor/composer/ClassLoader.php:347 Stack trace: #0 /Users/mikee/Documents/Projects/rangladex5/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(116): Illuminate\Foundation\Bootstrap\HandleExceptions->fatalExceptionFromError(Array) #1 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleShutdown() #2 {main}
I have not found a solution to FIXING this issue after issuing a composer dump-autload, all behat things are effectively broken?
Anybody have a cure by chance?
@JeffreyWay Should you want a 'broken' archive to review the extension, I would be most happy to provide it. This is driving me nuts.
I had the same issue, it was because I was using xDebug in PHPStorm. What I did was update the php.ini file and change the xdebug.max_nesting_level variable from 100 to 500 and that's it.
Got the solution from Jeffrey in another post ;)
Please or to participate in this conversation.