Buy Access to Course
27.

Testing a Form Submit

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

New feature request! On the homepage, management wants a form where they can choose an enclosure, write a dinosaur spec - like "Large herbivore" and submit! Behind the scenes, we will create that new Dinosaur and put it into the Enclosure.

Since we're now functional-testing pros, let's get right to the test! Add public function testItGrowsADinosaurFromSpecification(). And as usual, steal some code from earlier and paste it on top. You can start to see how some of this could be refactored to a setUp method.

// ... lines 1 - 8
class DefaultControllerTest extends WebTestCase
{
// ... lines 11 - 44
public function testItGrowsADinosaurFromSpecification()
{
$this->loadFixtures([
LoadBasicParkData::class,
LoadSecurityData::class,
]);
$client = $this->makeClient();
$crawler = $client->request('GET', '/');
$this->assertStatusCode(200, $client);
}
}

After creating the client, add $client->followRedirects(). Normally, when our app redirects, Symfony's Client does not follow the redirect. Sometimes that's useful... but this line makes it behave like a normal browser.

// ... lines 1 - 44
public function testItGrowsADinosaurFromSpecification()
{
// ... lines 47 - 52
$client->followRedirects();
// ... lines 54 - 57
}
// ... lines 59 - 60

Filling in the Form Fields

To fill out the form fields, first we need to find the form. Do that with $form = $crawler->selectButton() and pass this the value of the button that will be on your form. How about "Grow dinosaur". Then call ->form().

// ... lines 1 - 44
public function testItGrowsADinosaurFromSpecification()
{
// ... lines 47 - 58
$form = $crawler->selectButton('Grow dinosaur')->form();
}
// ... lines 61 - 62

We now have a Form object. No, not Symfony's normal Form object from its form system. This is from the DomCrawler component and its job is to help us fill out its fields.

So let's think about it: we will need 2 fields: an enclosure select field and a specification text box. To fill in the first, use $form['enclosure'] - the enclosure part is whatever the name attribute for your field will be. If you're using Symfony forms, usually this will look more like dinosuar[enclosure].

Then, because this will be a select field, use ->select(3), where 3 is the value of the option element you want to select. Do this again for a specification field. Setting this one is easier: ->setValue('large herbivore').

// ... lines 1 - 44
public function testItGrowsADinosaurFromSpecification()
{
// ... lines 47 - 59
$form['enclosure']->select(3);
$form['specification']->setValue('large herbivore');
}
// ... lines 63 - 64

Honestly, I don't love Symfony's API for filling in forms - I like Mink's better. But, it works fine. When the form is ready, submit with $client->submit($form). That will submit to the correct URL and send all the data up!

// ... lines 1 - 44
public function testItGrowsADinosaurFromSpecification()
{
// ... lines 47 - 62
$client->submit($form);
// ... lines 64 - 67
}
// ... lines 69 - 70

But... now what? What should the user see after submitting the form? Well... we should probably redirect back to the homepage with a nice message explaining what just happened. Use $this->assertContains() to look for the text "Grew a large herbivore in enclosure #3" inside $client->getResponse()->getContent().

// ... lines 1 - 44
public function testItGrowsADinosaurFromSpecification()
{
// ... lines 47 - 63
$this->assertContains(
'Grew a large herbivore in enclosure #3',
$client->getResponse()->getContent()
);
}
// ... lines 69 - 70

Test, done! Copy the method name and just run this test:

./vendor/bin/phpunit --filter testItGrowsADinosaurFromSpecification

Perfect! It fails with

The current node list is empty.

This is a really common error... though it's not the most helpful. It basically means that some element could not be found.

Code the Form

With the test done, let's code! And... yea, let's take a shortcut! In the tutorial/ directory, find the app/Resources/views/_partials folder, copy it, and paste it in our app/Resources/views directory.

<form action="{{ url('grow_dinosaur') }}" method="POST">
<div class="row">
<div class="column">
<label for="enclosure">Enclosure</label>
<select name="enclosure" id="enclosure">
{% for enclosure in enclosures %}
<option value="{{ enclosure.id }}">Enclosure #{{ enclosure.id }}</option>
{% endfor %}
</select>
</div>
<div class="column">
<label for="specification">Dino description</label>
<input type="text" id="specification" name="specification" placeholder="Small carnivorous dino friend" />
</div>
<div class="column">
<label for="">&nbsp;</label>
<input type="submit" class="button" value="Grow dinosaur" />
</div>
</div>
</form>

Then, at the top of index.html.twig, use it: include('_partials/_newDinoForm.html.twig').

// ... lines 1 - 5
use AppBundle\Factory\DinosaurFactory;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
// ... lines 8 - 11
class DefaultController extends Controller
{
// ... lines 14 - 27
/**
* @Route("/grow", name="grow_dinosaur")
* @Method({"POST"})
*/
public function growAction(Request $request, DinosaurFactory $dinosaurFactory)
{
$manager = $this->getDoctrine()->getManager();
$enclosure = $manager->getRepository(Enclosure::class)
->find($request->request->get('enclosure'));
$specification = $request->request->get('specification');
$dinosaur = $dinosaurFactory->growFromSpecification($specification);
$dinosaur->setEnclosure($enclosure);
$enclosure->addDinosaur($dinosaur);
$manager->flush();
$this->addFlash('success', sprintf(
'Grew a %s in enclosure #%d',
mb_strtolower($specification),
$enclosure->getId()
));
return $this->redirectToRoute('homepage');
}
}

The form is really simple: it's not even using Symfony's form system! You can see the name="enclosure" select field where the value for each option is the enclosure's id. Below that is the name="specification" text field and the "Grow dinosaur" button the test relies on.

For the submit logic, go back into the tutorial/ directory, find DefaultController and copy all of the growAction() method. Paste this into our DefaultController. Oh, and we need a few use statements: re-type part of @Method and hit tab to add its use statement. Do the same for DinosaurFactory.

// ... lines 1 - 5
use AppBundle\Factory\DinosaurFactory;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
// ... lines 8 - 11
class DefaultController extends Controller
{
// ... lines 14 - 27
/**
* @Route("/grow", name="grow_dinosaur")
* @Method({"POST"})
*/
public function growAction(Request $request, DinosaurFactory $dinosaurFactory)
{
$manager = $this->getDoctrine()->getManager();
$enclosure = $manager->getRepository(Enclosure::class)
->find($request->request->get('enclosure'));
$specification = $request->request->get('specification');
$dinosaur = $dinosaurFactory->growFromSpecification($specification);
$dinosaur->setEnclosure($enclosure);
$enclosure->addDinosaur($dinosaur);
$manager->flush();
$this->addFlash('success', sprintf(
'Grew a %s in enclosure #%d',
mb_strtolower($specification),
$enclosure->getId()
));
return $this->redirectToRoute('homepage');
}
}

Ok, it's happy! Sure, the code is lacking the normal security and safeguards we expect when using Symfony's form system... but it's only a dinosaur park people! We do, however, have the success flash message!

So if we haven't messed anything up, it should work. Try the test!

./vendor/bin/phpunit --filter testItGrowsADinosaurFromSpecification

Yes! It passes! We just confirmed that this form works before we ever even loaded it in a browser. That's pretty cool.

So that's the power of functional tests. And I find them especially powerful when using Mink and testing that my JavaScript works.

Ok guys, just one more topic left, and it's fun! Continuous integration! You know, the fancy term that means: let the robots run your tests automatically!