Chapters
-
Course Code
Subscribe to download the code!Compatible PHP versions: >=5.3.3
Subscribe to download the code!Compatible PHP versions: >=5.3.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Testing Forms
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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.
Testing Forms¶
One of the most common things you’ll want to do in a test is fill out a form. Start by using the crawler to select our submit button and get a Form object. Notice that we’re selecting it by the actual text that shows up in the button, though you can also use the id or name attributes:
public function testRegister()
{
// ...
// the name of our button is "Register!"
$form = $crawler->selectButton('Register!')->form();
}
In the browser, if we submit the form blank, we should see the form again with some errors. We can simulate this by calling submit() on the client and passing it the $form variable.
Tip
Both request() and submit() return a Crawler object that represents the DOM after making that request. Be sure to always get a new $crawler variable each time you call one of these methods.
Let’s test that the status code of the error page is 200 and that we at least see an error:
public function testRegister()
{
// ...
// the name of our button is "Register!"
$form = $crawler->selectButton('Register!')->form();
$crawler = $client->submit($form);
$this->assertEquals(200, $client->getResponse()->getStatusCode());
$this->assertRegexp(
'/This value should not be blank/',
$client->getResponse()->getContent()
);
}
Run the test again!
php bin/phpunit -c app
Beautiful!
Filling out the Form with Data¶
Let’s submit the form again, but this time with some data! Use selectButton to get another $form object.
Now, give each field some data. This is done by treating the form like an array and putting data in each field. These names come right from the HTML source code, so check there to see what they look like:
public function testRegister()
{
// ...
// submit the form again
$form = $crawler->selectButton('Register!')->form();
$form['user_register[username]'] = 'user5';
$form['user_register[email]'] = 'user5@user.com';
$form['user_register[plainPassword][first]'] = 'P3ssword';
$form['user_register[plainPassword][second]'] = 'P3ssword';
$crawler = $client->submit($form);
}
Now when we submit, the response we get back should be a redirect. We can check that by calling the isRedirect method on the response. Next, use the followRedirect() method to tell the client to follow the redirect like a standard browser. Finally, let’s make sure that our success flash message shows up after the redirect:
public function testRegister()
{
// ...
$crawler = $client->submit($form);
$this->assertTrue($client->getResponse()->isRedirect());
$client->followRedirect();
$this->assertContains(
'Welcome to the Death Star, have a magical day!',
$client->getResponse()->getContent()
);
}
Run the tests!
php bin/phpunit -c app
Success! We now have proof that we can visit the registration form and fill it out with and without errors. If we accidentally break that later, our test will tell us.
13 Comments
Hey Ivan,
The best practice is to use a different server to run your tests, but the environment should be the same (or very close to production environment). Also, the best practice is to use CI tools like Jenkins CI, etc. which you have to install on your server and then configure... or like CircleCI, TravisCI, etc - if you prefer cloud solutions.
Cheers!
Hi Ryan,
Is there a way to check if a certain element has a style attribute ? I'm trying to verify if a certain input is hidden for users proving that it has a "display: none" attribute.
I'm trying to implement "Honeypot" technique instead of a classic captchar for users registration.
Hey Diego!
Ah, honeypot! Cool! Personally, I usually test with Behat. But even if I write tests in pure PHP, I usually use Mink (which is a lot like what we show in this chapter, but a little bit easier to work with imo - Behat uses Mink also behind the scenes). In Mink, it's easy to check the style attribute on an element OR (if you use Selenium or Phantomjs) - to actually check if the element is visible. I can point you at some resources if you're curious in that direction.
For this style of testing, I think you'll need to find the element via CSS (the way that we fill in a field doesn't have any hook I can see to check the style attribute). Something like this:
$style = $crawler->filter('#some-field-id')->attr('style')
Let me know if that helps - I wrote the above code just-now, so it's possible it's not perfect ;).
Cheers!
Hey there!
Looks like is not that easy to achieve by just using the crawler object from the "WebTestCase" class
I ended with this:
$this->crawler->filter("input.".$this->honeyPotClass)->first();
$this->assertNotNull($honeypotInput);
Is not perfect because if someone screws up that class, my tests still will pass, but well, I can live with that at the moment hehehe
Welcome to the Death Star! Have a magical day! >> Welcome to the Death Star, have a magical day!
see https://knpuniversity.com/s...
The test fails because of small difference in these 2 texts. Still not improved
Got it fixed now at https://github.com/knpunive.... Thanks!
Hey! What is the best practice to test forms and controllers? Using real client functional tests that will also interact with a test db (sqlite) or mocking them and do fake db calls? Generally what is the best practice? Use unit tests and functional with mocks and then a feww functional and acceptance or do unit tests and the real functional and a few acceptance?
Also what if we have 3 roles in the system? Should we duplicate tests for each role?
Hi Nick!
Cool question :). I can tell you what I do, which of course I think is the best practice :).
1) Unit test your classes (*how* much you should unit test is subjective - I only unit test classes/functions that "scare" me ). Mock everything (but also see #3)
2) Functionally test your forms and controllers. I use a *real* HTTP client for this - i.e. Mink (often with Behat, but you could just use Mink). I no longer use Symfony's test functionality because Mink is able to open real browsers that test JavaScript. To test forms, I'm just testing the pages (controllers) that host those forms: fill out the form, hit submit, and see what happens. You can test these like crazy (test every different validation error situation), but I typically don't: I test that the form works when filled out correctly and (maybe) I have one test that shows that validation works in general. I rely on us as developers to double-check that we have all the validation rules on the form that we want. If you have some *really* complex, custom validation rule, sure, you can test that - or maybe it should be unit-tested :). About roles, I normally just login as a user that has access and try the form. If you want to test your security, I might recommend a separate test class that logs in as different users and tries to see if you can access restricted pages. If your form behaves differently based on roles, then I would test it multiple times (but only in this situation).
3) I also use "integration" tests occasionally. This is like a unit test, but I actually boot up Symfony and use the real service from the container (no mocking). This is useful, for example, test call a method on a Doctrine repository and test that the query returns the results you expect.
I tend to fall on the "pragmatic" side of testing - it's a tool, but I don't abuse it :).
Cheers!
This is potentially confusing part, so information for readers - if you are sending a form that creates an entity in test case like here, the entity will be created, and WON'T BE DELETED after completion of test. It means that, the test given here won't pass the second time, because there will be an error, that user with that name already exists - because it was created when test was run first time! To bypass this, you need to delete everything that you created during a test. I'm using 2.6.6 version of the symfony2 and here you can override for example protected tearDown() method in your test class, this method will run after the test so you can make a cleanup. Example content of this method:
$client = static::createClient();
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$entity = $em->getRepository('UserBundle:User')->findOneByUsernameOrEmail('testuser');
$em->remove($entity);
$em->flush();
Please correct me, if this approach is wrong, but this is how I understand things for now and it seems to work :) .
Ok, I can see now that this is explained in next screencast, so I was too hotheaded with this comment, move on :P .
Ha, cheers! You clearly are understanding things well then, if you see and solve the problem before the tutorial does ;).
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "~2.4", // v2.4.2
"doctrine/orm": "~2.2,>=2.2.3", // v2.4.2
"doctrine/doctrine-bundle": "~1.2", // v1.2.0
"twig/extensions": "~1.0", // v1.0.1
"symfony/assetic-bundle": "~2.3", // v2.3.0
"symfony/swiftmailer-bundle": "~2.3", // v2.3.5
"symfony/monolog-bundle": "~2.4", // v2.5.0
"sensio/distribution-bundle": "~2.3", // v2.3.4
"sensio/framework-extra-bundle": "~3.0", // v3.0.0
"sensio/generator-bundle": "~2.3", // v2.3.4
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"doctrine/doctrine-fixtures-bundle": "~2.2.0", // v2.2.0
"ircmaxell/password-compat": "~1.0.3", // 1.0.3
"phpunit/phpunit": "~4.1" // 4.1.0
}
}
Hi.
What's the best practice in terms of a testing environment? I mean, where I should run tests - on development server, on production server or both?