Buy

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.

Leave a comment!

  • 2016-05-07 weaverryan

    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!

  • 2016-05-06 Nick

    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?

  • 2016-04-13 Diego Aguiar

    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

  • 2016-04-13 weaverryan

    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!

  • 2016-04-12 Diego Aguiar

    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.

  • 2015-09-10 weaverryan

    Got it fixed now at https://github.com/knpuniversi.... Thanks!

  • 2015-09-10 guest

    The test fails because of small difference in these 2 texts. Still not improved

  • 2015-07-10 guest

    Welcome to the Death Star! Have a magical day! >> Welcome to the Death Star, have a magical day!
    see https://knpuniversity.com/scre...

  • 2015-05-04 weaverryan

    Ha, cheers! You clearly are understanding things well then, if you see and solve the problem before the tutorial does ;).

  • 2015-05-03 Łukasz Zaroda

    Ok, I can see now that this is explained in next screencast, so I was too hotheaded with this comment, move on :P .

  • 2015-05-03 Łukasz Zaroda

    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 :) .