Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ? valid, but the code we use can't be used in a real application.

Handling Data in Tests

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Handling Data in Tests

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.

Using Background to Add a User

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.

Test: GET One Programmer

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!

Test: GET all Programmers

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!

Leave a comment!

12
Login or Register to join the conversation
Beniamin T. Avatar
Beniamin T. Avatar Beniamin T. | posted 4 years ago

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

Reply

Hey Beniamin!

What is your question here? It's not clear enough what you are trying to say us in the comment.

Cheers!

1 Reply
Default user avatar
Default user avatar argy_13 | posted 5 years ago

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!

Reply

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!

1 Reply
Default user avatar

Also a few lines later the same command

Reply
Markus L. Avatar
Markus L. Avatar Markus L. | posted 5 years ago

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 ?

Reply

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?

1 Reply
Markus L. Avatar

Even GREATER answer. Smooth and elegant solution - I'll try that. Thnxbye :)

Reply
Default user avatar
Default user avatar Tael Kim | posted 5 years ago

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

Reply

That's right Tael! Just surround any % characters with quotes and you'll be just fine now and going forward :)

Reply
Default user avatar
Default user avatar Sarang Bondre | posted 5 years ago

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

Reply

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!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ? valid, but the code we use can't be used in a real application.

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice