BDD, Behat (version 2.5), Mink and other Wonderful Things

Buy Access
UPGRADE! Check out the newest version of this tutorial

If you own this tutorial and want to upgrade to the latest Behat 3 version, send us an email/contact message and we'll be happy to give you a coupon code to order the 3.0 version absolutely free :). Yay upgrading!

Ready to revolutionize how you develop? In this tutorial, you'll internalize Behavior-Driven Development and how thinking about behavior will increase the quality and efficiency of your code. You'll become an expert in Behat - the BDD PHP framework - and Mink - your key to testing web applications in sophisticated ways that include the ability to test your JavaScript pages. We'll also explore the pitfalls of testing with Behat, such as data sanitization, bootstrapping your code, and other challenges. By the end, you'll be well on your way to writing high-quality tests for your application and - more importantly - approaching development in a totally new light.

Behat works great with any PHP project, but if you're using Symfony2, we'll give you some special tips to get you rolling.

Your Guides

Ryan Weaver Leanna Pelham

Questions? Conversation?

  • 2015-10-19 Patrick O'Sean

    Nice, thanks I will have a look at phpspec and your upcoming screencast on it. Regards

  • 2015-10-19 weaverryan

    Hey Patrick!

    Theoretically, I think you've nailed it (though, you should take a look at PhpSpec for unit tests - we'll have a tutorial on that soon). However, I am not a huge fan of unit testing. I mean, yes, I do them - but only when I'm writing a function that "scares" me. In other words, if a function is pretty straightforward, then I rely on my Behat scenarios (tests) to see that the whole system is working. Your 1/2/3 order is perfect, but I would only do 2 when you feel you need to. Though, with PhpSpec, (2) becomes less about "testing" and more about "helping you design your classes", which is worthwhile on its own.


  • 2015-10-16 Patrick O'Sean

    Hey Ryan, I've read and seen a lot about TDD and BDD now. My question is, how do I start now implementing a new feature. Let's say I have a dvd store and want to be able to add new dvds to the inventar. This process requires maybe different methods. My approach would be:
    [1] Create BDD behat scenario / functional test for adding a new dvd
    [2] Create a unit test with phpunit for each method that is required for adding a new dvd
    [3] Create the actual code for adding a new dvd

    For me, this would be a combination of BDD first and TDD afterwards? Or could you please explain me the correct "theoretical" way if I misunderstood something.
    Regards, Patrick

  • 2015-07-15 weaverryan


    Yep, I *do* use both: Behat for functional tests and PHPUnit for unit tests. I end up writing mostly functional tests, so I use Behat quite a bit more. That may be how you're feeling as well :).

  • 2015-07-15 Victor Bocharsky

    Hi, Ryan!

    Does it normal to use both PHPUnit and Behat tests in one project? Or better keep up only one testing framework for all tests?
    I started to use Behat and began to wonder why I need a PHPUnit anymore. It seems a bit confused to use them both in one project, doesn't it?

  • 2015-04-13 weaverryan

    Well hi Inna!

    Interesting! So, Behat should be able to "autoload" anything that Composer autoloads (e.g. anything that follows PSR-4 or PSR-0). It looks like you're using this library:, and so if you bring this in via Composer, then the autoloading should work out of the box. This is exactly how the MinkExtension is loaded too.

    So, are you installing this via Composer? Can you, on some working page in your site (not in Behat), instantiate a new Extension object manually (if you can, then you should be able to reference it in behat.yml).


  • 2015-04-13 Inna

    Hi All!

    I'm trying to use BusinessSelectorExtension. I add to behat.yml file:

    selectorFilePath: dictionary.yml
    timeout: 10
    UIBusinessSelector: ~

    But I get:

    [Behat\Testwork\ServiceContainer\Exception\ExtensionInitializationException] `OrangeDigital\BusinessSelectorExtension\Extension` extension file or class could not be located.

    Behat v3.0.15

    What am i doing wrong?

  • 2014-12-04 weaverryan

    Hey Sai!

    So you're using something like a custom jQuery date-picker? The solution depends. First, you'll of course need a custom step for this, e.g.:

    And I fill in the "Birthday" date picker with "20/12/1985"

    For the implementation, you have 2 choices. First, you'll find the correct field based on the label - something like this (assuming $fieldLabel is the value passed to your function for "Birthday"):

    $field = $this->getSession()->getPage()->findField($fieldLabel);

    Then, you have 2 choices:

    1) Use a bunch of CSS to find different physical elements of your calendar and click those. In other words, do exactly what the user does - click some icon, then click to change the month, etc).

    2) Use JavaScript directly. Most of the time, a date-picker has some JS API you can use to set the date. I'm inventing this API, but for example:

    '$("#%s").datepicker("set", "%s");',

    I hope that helps!

  • 2014-12-02 Sai Satish

    I'm trying to select the date from datepicker/calendar for Date of Birth field. However, unable to do it. Can anyone suggest me with the possible working code for "selecting a required date from the calendar for DOB field.

  • 2014-08-04 codedungeon

    It's all good! Since posting the comment, I have since figured everything out and have a good grasp on the changes between v2 and v3. As you stated, mostly just configuration items.
    As we discussed on twitter, I am working on a conversion guide between v2 and v3 so was looking for any information on v3 that I could find. As with most programming tasks, it required a lot of trial and error but all is good now!
    - mse

  • 2014-08-04 weaverryan

    You're right :). We haven't started on it yet, but we definitely will soon. The good news is that it's not too much different. The installation and configuration are the biggest changes, but the underlying usage is all pretty much the same :). In the mean-time, I do have a video presentation on using Behat v3 if you're curious :)


  • 2014-08-03 codedungeon

    So, it seems that Behat3 is more than stable at this point! Have you done any work towards upgrading your course to use Behat3

  • 2014-07-22 weaverryan

    Hey Michael!

    Wow, that's really interesting. It's not totally uncommon for things to work differently between JavaScript and non-JavaScript, because there are subtle differences - e.g. if a button is "hidden" with CSS in JavaScript, Selenium (correctly) won't find it, but Goutte will. But I *am* surprised that you've found something that works in Sahi, but not in Selenium. I use Selenium for everything and find it very dependable, but obviously you've found something here that's definitely wonky :). I wouldn't necessarily give up on Selenium, but Sahi is also very good (though it doesn't execute quite as quickly in my experience).

    Thanks for the comment!

  • 2014-07-21 Michael

    The Velociraptor example with Selenium2 works, but when I search "OHM (band)" for "David Eagle" (copied from the page source), it fails. If I match on another band member, "Chris Poland", it works. The match on "David Eagle" works in Goutte ("@javascript" commented out). I'm running selenium-server-standalone-2.42.2.jar. I verified that selenium-server-standalone-2.39.0.jar is also defective. I installed sahi and it works so I will not be using Selenium again.

  • 2014-04-24 weaverryan

    Hi there!

    For the most part, yes. The vast majority of things are not changing: the scenarios, philosophy, mapping to functions, Mink, etc. There will be some changes - setup/configuration, subcontexts and chained steps - but these are small pieces of the full Behat picture :). We'll also upgrade the tutorial once Behat 3 is stable, and you can come back then to see those updates :).

    I hope that helps!

  • 2014-04-21 codedungeon

    Will these procedures still work with Behat 3, despite their recent changes? Looking for a few good Behat tutorials but want to make sure they work with Behat 3

  • 2014-02-06 weaverryan

    Hi Anamika!

    I'm not sure what the issue is :). I would:

    1) Find the #large field via Mink and set the file on it (using the method we talked about earlier).

    2) I would trigger the upload however the user does - click a button, etc. I'm not sure about this part, because while I see a button that will open the file upload "Browse" box, I don't see any button or JavaScript that would then submit the form or start the upload. But basically, the issue is not how to browse and select the right file - that should be taken care of with (1) - it's about taking whatever user action after this to actually submit the form or start the file upload. For this part, just do whatever the user does - that will take care of you 99% of the time.


  • 2014-02-06 Anamika

    Yeah sorry had to stop this work and moved to another, back this again!!! Ya you are right, i tested file upload process it works fine with behat/mink (I attach the file..), makes ajax call etc. But found that issue is different, here following is the structure of file upload in my application. There you can see actual file filed is invisible where as browse button invoke the file filed event. In which case test scenario (I attach the file) is failing. Through javascript also can't write to file field where browser doesn't allow for security reason.

    <input type="file" style="display:none" placeholder="Filename ..." id="large" name="largeUploadImage" class="form-control fileUpload">
    <input type="text" placeholder="Filename ..." id="largeImgFile" class="form-control">

    <button onclick="$('input[id=large]').click();" type="button" class="btn btn-primary">Browse</button>

    Hope you got the issue! Thank you in advance :)

  • 2014-01-24 weaverryan

    It sounds like this is your situation: when the user selects a file, then some JavaScript causes an AJAX POST request to be made in the background. Is that accurate?

    If so, first, obviously make sure you're running the tests in Selenium so that JavaScript fires. I'm sure you're already doing this, but I wanted to mention it! Second, if you're JavaScript is written to fire on the "change" event of the file upload, then I would *think* that attaching the file via Mink would cause this event to trigger (and so your JavaScript would run). I've never tried that with a file upload field, so I can't be sure - but generally-speaking, if you click something or fill out some form field with Behat, that is really happening in a real browser, so it triggers all the same JavaScript events as normal. If this is *not* the case for the file upload box, then yes, I would work around it by manually executing JavaScript that triggers the "change" event on the file upload field. That's no ideal, but if setting the file isn't triggering the event, then you'll need to.

    I hope that helps!

  • 2014-01-24 Anamika

    Hey Weaverryan,
    Thank you it works!! Now strucked up with another query, in my application once the file (image file) is selected makes an API call (post method) which takes care of storing file into respective folder. But API call is not happening through test scenarios, so trying to invoke the API call using executeScript. But it doesn't working out for me. So how can i solve it now? wanted your thoughts and suggestions on this. Waiting!!!!!!!!!!!!!!!!!!!

  • 2014-01-22 weaverryan

    Hi Anamika!

    Yes, you're using the correct statement! Behind the scenes, when you say "I attach the file 'filepath' to 'field'", the "field" is found by its label name (like always) and then this method is called on that element:

    When you use that method directly, you give it the absolute file path to the file on your system that you want to upload. Of course, including a full-path in a scenario is not great - we want to keep those ugly details hidden. So when you use this inside Behat, you should say something like:

    I attach the file "foo.pdf" to "Upload file"

    Behat will look in a specific directory for "foo.pdf" - this directory is defined in your behat.yml file under the "files_path" key: So in your behat.yml, under the MinkExtension configuration (so right next to selenium2: ~, goutte: - see example here: - add a `files_path` key, and point that to some directory. Then, put your `foo.pdf` in that directory.

    I hope that helps out :). Whenever your not totally sure how something is working, it's a good idea to look in the MinkExtension class and see what the code there is doing:

    Good luck!

  • 2014-01-22 weaverryan

    Hey Thomas

    I'm sorry you were disappointed with the missing Symfony2 pieces :/. I'm going to look into that and see if the description needs to be updated - that may have been my bad :).

    Installing PHPUnit is historically a pain because it was done via Pear (which is a pain). However, 2 things:

    1) You don't need PHPUnit - it's just an extra thing that give you the assert_equals, etc functions - we talk about that briefly here: If you *aren't* using PHPUnit, then you can just do the checks yourself. For example:

    assertEquals($a, $b, 'These are not equal!')

    Would become

    if ($a != $b) {
    throw new \Exception('These are not equal');

    2) The best way to install PHPUnit (and now the recommended way) is via Composer: If you do this, PHPUnit (and those files) are in your vendor directory. The require statements in this case should look like:

    require_once __DIR__.'/../../vendor/phpunit/phpunit/PHPUnit/Autoload.php';
    require_once __DIR__.'/../../vendor/phpunit/phpunit/PHPUnit/Framework/Assert/Functions.php';

    The other require statements that Behat adds by default only work if (a) you have PHPUnit installed via Pear and B) Have all your PHP-Pear include path setup (which isn't always easy).

    I hope that helps out! About Symfony2 - the key is the Symfony2Extension: What it gives you (most importantly) is access to Symfony's Container from within your FeatureContext class. Here's how that would look once you have that extension installed:

    I'll open an issue about adding some of these notes (e.g. Symfony2 integration, more info on PHPUnit) - I think you have good questions, so the notes will almost definitely be useful for others. Issue:


  • 2014-01-22 Anamika

    Hi there, I suppose to write scenario for image upload, i used behat "I attach the file 'filepath' to 'field'" but it doesn't work. Please help me.

  • 2014-01-22 Thomas

    I am a little disappointed with this screencast. It did teach a lot about behat and mink and I learned a lot. But this page in the description it says if I am using Symfony2 there'll be special tips to get me rolling. There were none. Instead, the framework used was Silex, which is based on Symfony2, I understand, but I was expecting information directly with Symfony2.

    I am also having a hell of a time getting PHPUnit to work with Behat. I commented out the require_once lines and then the tests don't run at all. I have PHPUnit installed and even gave the absolute path starting with the drive letter on my windows box and it doesn't seem to work at all. It just displays the Scenario line of the feature and doesn't move on. It doesn't even say that so many steps were run or anything.

  • 2014-01-22 Thomas

    For anyone else using windows. behat-ls feature won't work for us. I created an issue on github and hopefully we can figure it out. :)

    I did find out the command in windows that works like rm -r try:
    `system('rd /S /Q '.realpath('test'));`

    It worked well for me.

  • 2014-01-21 weaverryan

    Hi theMan

    No worries :). The PDF download is simply a written version of what the spoken audio is in the video. So both are the same. But, I'm not sure if that's what you're talking about exactly. So that we can get you going, can you email me - ryan[at]


  • 2014-01-21 theMan

    Thank's for that Ryan. I'm kinda concerned if I might be asking too much questions. I'm not from a developer background, but I've also bought the PHP to help with the B§8§§ehat course. But right now I'm struggling with the method functions that helps identify Xpath of fields to edit, and click. I've tried the video and the PDF files but it seems like plenty of alternatives were tried and it looks distracting.

    Can you please identify the best path to follow from the video and the pfd, e.g. page no and stage in video.


  • 2014-01-21 weaverryan

    Hi Femi Fet!

    It's just *1* dash - "php bin/behat -dl". But that's my fault - I had a typo with two dashes in the intro chapter. I've added a quick issue, which I'll fix and deploy today:

    Thanks for asking the question!

  • 2014-01-21 weaverryan

    Ah, my mistake! I'll answer it right now!

  • 2014-01-18 theMan

    When I try to run, php bin/behat --dl, I get the error below:


    The "--dl" option does not exist.

    behat [--init] [-f|--format="..."] [--out="..."] [--lang="..."] [--[no-]ansi] [--[no-]time] [--[no-]paths] [--[no-]snippets] [--[no-]snippets-paths] [--[no-]multiline] [--[no-]expand] [--story-syntax] [-d|--definitions="..."] [--name="..."] [--tags="..."] [--cache="..."] [--strict] [--dry-run] [--rerun="..."] [--append-snippets] [features]

  • 2014-01-18 theMan

    You deleted the wrong question, and that was a legitimate one with the --dl query.

  • 2013-12-29 weaverryan

    Hi there!

    Hmm. If you're not running the Selenium server in the background, that would certainly cause the browser to not load. BUT, you'll also receive a huge error :).

    If it *is* running and you're in Behat, make sure you have the `@javascript` above your scenario. If you're using Mink directly, make sure you're using the `Selenium2Driver`, not the `GoutteDriver` - one of these two is probably the cause if you don't see a big error.

    Good luck!

  • 2013-12-29 Guest

    Hi, everything worked, but the browser didn't load up. What am I missing here?

  • 2013-12-17 weaverryan

    Hey Maksim!

    That's very interesting! I haven't benchmarked this, but there could be 2 reasons:

    1) Obviously, you skip the parsing of the English. This will take some time, but I'm not sure how much. It wouldn't seem like too much, but it could be.

    2) The built-in code uses a big (but awesome) xpath statement internally to find the fields based on their label or a bunch of other things. Your xpath uses only the id, which is quite fast.

    I'd be careful to not do too much of this, because you'll not have as much code-reuse and the xpaths used here aren't as flexible as the ones used internally. Plus, you're probably going to lose most of your time opening up a browser if your tests use JavaScript.

    That being said, since logging in is really common, making a tweak like this could save time. I'd probably do it the easy way first, then modify things later if you want to. But regardless, this is an interesting comment!


  • 2013-12-12 Maksim Borissov

    Apparently, doing iAmLoggedIn functionality with:

    $page = $this->getMink()->getSession()->getPage();

    $username = $page->find('xpath', '//input[@id="username"]');

    $password = $page->find('xpath', '//input[@id="password"]');

    $loginButton = $page->find('xpath', '//button[@id="login"]');

    would be much faster than

    return array(
    new Given('I am on "login"'),
    new Given('I fill in "Username" with "Ryan"'),
    new Given('I fill in "Password" with "foobar"'),
    new Given('I press "Login"'),

    It looks a bit more uglier and needs some knowledge about manipulating DOM but on my setup, with my own tests, I get 10-20 less execution time.

    I'm using Behat/Mink + Sahi Driver

  • 2013-10-25 weaverryan

    Hi Vishal!

    I don't think it's a bug - it's just that if you find something in the zero position, it returns 0, which == false. So you're right that the original code in the text never worked :) - the code in the actual video (and now in the code download) uses === false and works nicely now.


  • 2013-10-25 Vishal Khialani

    array_search() seems to have a bug in it for any value which is in key '0' I am not sure how yours worked. ref :

  • 2013-09-23 weaverryan

    Hi Felipe!

    Ah, thanks for the kind words and taking the time with the notes here :).

    1. I have never seen this before, but you're right! In Firefox, changing the font size makes the video player act out. Not cool - I've added this as a bug in our tracker.

    2. I agree - it would be helpful to leverage the different sections (which already exist) to organize things even better. If I understand you correctly, you're proposing showing a 1-2 second "slide" in the middle of the video as you move from section to section. Is that right? I'd also like to mark the different sections on the video or have an in-video TOC that's clickable - it's something we have on our minds :).

    Any other thoughts are always warmly appreciated.

    Thanks again!

  • 2013-09-22 Felipe

    Everything worked!

    Some points to consider in making everything easier:
    1. If I change the font size in my Chrome browser, clicking on a new video position goes crazy. I struggled for a while with it.

    2. Since the screencast has several sections and we need to go back, it would be great to have some section heading slides to clearly mark the beginning of a new section/task. Jumping directly to a section would also be great.

    All in all it is a great effort and will surely have a huge impact in how we develop.

  • 2013-09-03 weaverryan

    Awesome, thanks for the head's up Mark - I've fixed it at


  • 2013-08-31 Mark Fox

    Here's a little error. In your script for the "behat" chapter you use "!array_seach(…)" which evaluates to false on the zero indexed item (since "0" will be cast as a boolean) — in the video you correctly used "array_searc(…) === false" so your test worked and passed just fine ;-)

  • 2013-08-31 Mark Fox

    I agree. You do a great job of showing the progression and getting hung up on the details is not important to learning the concepts. The comment you added in your commit should help anyone who goes step by step and likes to poke around like I did.

  • 2013-08-30 weaverryan

    Hi Mark!

    Haha, so it's a little teaching trick that maybe didn't work so well in this case :). The idea is that people understand the purpose of __construct, so it helps them get the idea that this is "setup" stuff. But of course, as you outlined perfectly with the double-instantiation, it's not the "right" way.

    Anyways, I'm glad you figured it out - you probably also understand more about when a FeatureContext is instantiated than most people do. I've also added a small note to the script about this (


  • 2013-08-30 Mark Fox

    Kind of a pesky bit of feedback but since the FeatureContext seems to be constructed before evaluating the feature files and then again to run the tests your example code ends up creating, and changing into the 'test' directory twice… first time I checked this out to see what was going on I thought I was doing something wrong. I see the utility of using the constructor and then introducing the BeforeScenario tag, but as a curious novice it did give off a code smell seeing the consequence of using the constructor in that situation.

  • 2013-08-26 weaverryan

    Hi Pablo!

    I'm glad you worked it out and thanks for posting the extra details here! Bad luck that it was corrupted :).


  • 2013-08-23 Pablo María Martelletti

    Hi Ryan!

    Actually, if you move the driver into /usr/local/bin/ it should work out-of-the-box, without starting the driver manually, just starting selenium.

    I've followed the steps that you mentioned in your anwser, and it did work! So, i've tried moving it into /usr/local/bin/ again.. and did work this time! Finally, i've tried to execute the driver I was using until today and... it turned out that it was corrupted :S So that was the real problem :S

    Thanks for your response! Appreciate!

  • 2013-08-23 weaverryan

    HI Pablo!

    What issues are you having? I *have* gotten Chrome to work in Linux before, but sometimes using Chrome takes a bit more work. Specifically, you'll need the ChromeDriver ( If I remember correctly, you basically need to download this, execute it (it runs like a daemon, like Selenium), then also start Selenium in another terminal, then try things again.

    Let me know if that works out! And if you have any errors, put them here!


  • 2013-08-22 Pablo María Martelletti

    Hi there! Is there any way to configure selenium to open Google Chrome, or Chromium, under Linux? Because I just cant do it work, and everytime selenium opens firefox to try a feature, it takes a lot!

    Thanks for your help!

  • 2013-06-01 Matthew Fisher
  • 2013-05-30 Matthew Fisher

    I was there at the talk as well. You have really inspired me to start using and practicing BDD with my applications (PHP and beyond), and to promote others to do the same as well. It definitely lit some fires under some of the dev's feet, as Dave had said. I'll also be talking a little bit about the talk in a company blog, so look out for it on

    From the other person in the audience who knew Leanna. :)

  • 2013-05-24 Francisco

    I was at the DrupalconPDX talk also (front row w/glasses). I had to leave during the Q&A and did not get a chance to say thank you. One of the developers in my team was in the audience as well. He later told me that your talk clarified some concepts and as a result will alter the planned approach for an upcoming project. I'm sure that saved us some time. Great talk!

  • 2013-05-24 Dave Hurley

    Great talk at DrupalCon Portland, Ryan! I saw lots of animated discussions afterwards. You lit some fires.

    From one of the two people in the audience who knew who Leanna is.

  • 2013-05-12 Quentin Pleplé

    Awesome video! You totally convinced me.

  • 2013-02-19 KnpUniversity

    HI Vamsi - No problem, we've got that updated for you!

  • 2013-02-12 Krishna

    hey guys add some more years to your cc processing form