If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Let’s run our test a second time:
$ php vendor/bin/behat
It fails! The nickname of a programmer is unique in the database, and if you look closely, this fails because the API tries to insert another ObjectOrienter and blows up. To fix this, add a new function in ApiFeatureContext with a special @BeforeScenario anotation above it:
// features/api/ApiFeatureContext.php
// ...
/**
* @BeforeScenario
*/
public function clearData()
{
$this->getProjectHelper()->reloadDatabase();
}
The body of this function is specific to my app - it calls out to some code that truncates all of my tables. If you can write code to empty your database tables, or at least the ones we’ll be messing with in our tests, then you can do this.
Tip
In order to access your framework’s normal database-related functions, you’ll need to bootstrap your app inside this class. For many frameworks, libraries exist to glue Behat and it together. If you have issues or questions, feel free to post them in the comments.
The @BeforeScenario annotation, or comment, tells Behat to automatically run this before every scenario. This guarantees that we’re starting with a very predictable, empty database before each test.
Try the test again:
$ php vendor/bin/behat
Dang, it failed again. Ah, remember how we’re relating all programmers to the weaverryan user? Well, when we empty the tables before the scenario, this user gets deleted too. That’s expected, and I already have a sentence to take care of this. Uncomment the Background line above the scenario. This runs a function that inserts my user:
# features/api/programmer.feature
Feature: Programmer
# ...
Background:
Given the user "weaverryan" exists
# ...
Eventually we’ll have many scenarios in this one file. Lines below Background are executed before each Scenario. Ok, try it one more time!
$ php vendor/bin/behat
Success! When you test, it’s critical to make sure that your database is in a predictable state before each test. Don’t assume that a user exists in your database: create it with a scenario or background step.
And, every test, or scenario in Behat, should work independently. So don’t make one scenario depend on the data of a scenario that comes before it. That’s a huge and common mistake. Eventually, it’ll make your tests unpredictable and hard to debug. If you do a little bit of work early on to get all this data stuff right, you and Behat are going to be very happy together.
Let’s add a second scenario for making a GET request to view a single programmer. This entirely uses language that I’ve already prepped for us:
# features/api/programmer.feature
# ...
Scenario: GET one programmer
Given the following programmers exist:
| nickname | avatarNumber |
| UnitTester | 3 |
When I request "GET /api/programmers/UnitTester"
Then the response status code should be 200
And the following properties should exist:
"""
nickname
avatarNumber
powerLevel
tagLine
"""
And the "nickname" property should equal "UnitTester"
The Given statement actually inserts the user into the database before we start the test. That’s exactly what I was just talking about: if I need a user, write a scenario step that adds one.
The rest of the test just checks the status code and whatever data we think is important, just like in the previous scenario.
Run it!
$ php vendor/bin/behat
Success!
We’re on a roll at this point, so let’s add a third scenario for making a GET request to see the collection of all programmers. Oh, and the title that we give to each scenario - like GET one programmer: is just for our benefit, it’s not read by Behat. And for that matter, neither are the first 4 lines of the feature file. But you should still learn more about the importance of these - don’t skip them!
# features/api/programmer.feature
# ...
Scenario: GET a collection of programmers
Given the following programmers exist:
| nickname | avatarNumber |
| UnitTester | 3 |
| CowboyCoder | 5 |
When I request "GET /api/programmers"
Then the response status code should be 200
And the "programmers" property should be an array
And the "programmers" property should contain 2 items
Here, we insert 2 programmers into the database before the test, make the HTTP request and then check some basic things on the response. It’s the same, boring process over and over again.
I hope you’re seeing how awesome testing our API with Behat is going to be!
Hey Beniamin!
What is your question here? It's not clear enough what you are trying to say us in the comment.
Cheers!
Hello guys! just to mention something
Your first phrase when you have written the video speech, under the video is:
Let’s run our test a second time:
$ php vendir/bin/behat
but of course the command to the terminal is
$ php vendor/bin/behat
Cheers!
Hey argy_13 ,
Ah, a silly misprint and then its copy/paste in a few more places. Thanks for let us know! I fixed it in: https://github.com/knpunive...
Cheers!
Hiya ! How can I target my test database via behat ? Is there a specific behat config file for this ? Or do I reference a specific test environment when running behat ?
GREAT question. Unless you're using the "symfony2" driver via the Symfony2Extension (which I don't use), Behat is making *real* HTTP requests. So, you'll need to create a new app_test.php file that boots Symfony in the "test" environment. Then, go into your config_test.yml file and override doctrine's "dbname" key to be a different database name. Finally, update behat.yml to use this app_test.php in your base URL - like localhost:8000/app_test.php
Does that make sense?
NOTE THAT: yml config syntax will be changed.
Not quoting a scalar starting with the "%" indicator character is deprecated since Symfony 3.1 and will throw a ParseException in 4.0
That's right Tael! Just surround any % characters with quotes and you'll be just fine now and going forward :)
Am getting this error
UserBundle\Tests\Controller\RegisterControllerTest::testRegister
Failed asserting that false is true.
FAILURES!
Tests: 3, Assertions: 7, Failures: 1.
Remaining deprecation notices (2)
UserBundle\Form\RegisterFormType: The FormTypeInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeInterface with Symfony 3.0: 2x
2x in RegisterControllerTest::testRegister from UserBundle\Tests\Controller
Hi Sarang!
Ah, this is actually pretty cool! When something changes in Symfony (e.g. from version 2.7 to 2.8), instead of just changing it and breaking your code, Symfony deprecates the old functionality. AND, it also tells you about any deprecated code paths when you run your tests so you know about them and can upgrade :). Some things that we show in this tutorial are deprecated now (we're working on new versions currently), and this is a great example. In RegisterFormType, you should rename setDefaultOptions to configureOptions - here's an example (look at the first 2 code blocks): https://github.com/symfony/...
When you fix the deprecation, your tests will pass :). This is actually one of the *killer* features of Symfony. If you want to learn more about it, this is a great presentation about it: http://www.slideshare.net/n...
Cheers!
// composer.json
{
"require": {
"silex/silex": "~1.0", // v1.3.2
"symfony/twig-bridge": "~2.1", // v2.7.3
"symfony/security": "~2.4", // v2.7.3
"doctrine/dbal": "^2.5.4", // v2.5.4
"monolog/monolog": "~1.7.0", // 1.7.0
"symfony/validator": "~2.4", // v2.7.3
"symfony/expression-language": "~2.4" // v2.7.3
},
"require-dev": {
"behat/mink": "~1.5", // v1.5.0
"behat/mink-goutte-driver": "~1.0.9", // v1.0.9
"behat/mink-selenium2-driver": "~1.1.1", // v1.1.1
"behat/behat": "~2.5", // v2.5.5
"behat/mink-extension": "~1.2.0", // v1.2.0
"phpunit/phpunit": "~5.7.0", // 5.7.27
"guzzle/guzzle": "~3.7" // v3.9.3
}
}
Feature: Programmer
In order to battle projects
As an API client
I need to be able to create programmers and power them up
Background: # features\api\programmer.feature:6
Given the user "weaverryan" exists # ProjectContext::theUserExists()
Scenario: Create a programmer # features\api\programmer.feature:9
Given I have the payload: # ApiFeatureContext::iHaveThePayload()
"""
{
"nickname" : "ObjectOrienter",
"avatarNumber" : 12,
"tagLine" : "I'm from a test!"
}
"""
When I request "POST /api/programmers" # ApiFeatureContext::iRequest()
Then the response status code should be 201 # ApiFeatureContext::iGetAResponse()
And the "Location" header should be "/api/programmers/ObjectOrienter" # ApiFeatureContext::theHeaderShouldBe()
And the "nickname" property should equal "ObjectOrienter" # ApiFeatureContext::thePropertyEquals()
Scenario: GET one programmer # features\api\programmer.feature:23
Given the following programmers exist: # ProjectContext::theFollowingProgrammersExist()
| nickname | avatarNumber |
| UnitTester | 3 |
When I request "GET /api/programmers/UnitTester" # ApiFeatureContext::iRequest()
Then the response status code should be 200 # ApiFeatureContext::iGetAResponse()
Output is "text/html; charset=UTF-8", which is not JSON and is therefore scary. Run the request manually.
Failed asserting that 404 is identical to 200.
And the following properties should exist: # ApiFeatureContext::thePropertiesExist()
"""
nickname
avatarNumber
powerLevel
tagLine
"""
And the "nickname" property should equal "UnitTester" # ApiFeatureContext::thePropertyEquals()
Failure! when making the following request:
GET: http://localhost:8000/api/programmers/UnitTester
Failure! Below is a summary of the HTML response from the server.
Sorry, the page you are looking for could not be found.
1/1
NotFoundHttpException in BaseController.php line 171:
Oh no! This programmer has deserted. we'll send a serch patrol to search him
2 scenarios (1 passed, 1 failed)
12 steps (9 passed, 2 skipped, 1 failed)
0m2.255s