Developing robust web applications with automated testing in Symfony and PHPUnit - Part 2

In the previous chapter we looked at setting up a reproducible test environment by using Doctrine fixtures to set up the database into a known state before each test. In this chapter we'll use these fixtures in some automated functional tests using the Symfony client and crawler components to simulate a user browsing a web application.

Example - developer list page

The example we'll use to build our first test for will be a simple page that lists all developers that are present in the database in a simple HTML table.

The page will use the following Twig template, which prints a table containing all of the developers passed to it:

  <html>
    <head>
      <title>Developer list</title>
    </head>
    <body>
      <h1>Software developers in Birmingham, West Midlands</h1>

      <table class="developer-list">
        <thead>
          <th>Username</th>
          <th>Bio</th>
        </thead>

        <tbody>
          {% for developer in developers %}
            <tr>
              <td>{{ developer.username }}</td>
              <td>{{ developer.bio }}</td>
            </tr>
          {% endfor %}
        </tbody>
      </table>
    </body>
  </html>

The previous template is rendered by the listAction in the DeveloperController:

  namespace Codevate\DeveloperBundle\Controller;

  use Symfony\Bundle\FrameworkBundle\Controller\Controller;

  class DeveloperController extends Controller
  {
    /**
     * Show a list of all developers in the database.
     */
    public listAction()
    {
      // Get the developer repository
      $entityManager = $this->getDoctrine()->getManager();
      $developerRepository = $entityManager->getRepository('CodevateDeveloperBundle:Developer');

      // Fetch all developers in the database, sorted by username
      $developers = $developerRepository->findBy(array(), array('username' => 'ASC'));

      // Show the view
      return $this->render(
        'CodevateWebsiteBundle:Developer:list.html.twig',
        array(
          'developers' => $developers
        )
      );
    }
  }

Testing the developer list

Symfony's client component can be used to simulate a user's web browser. You are able to ask it to request pages by their URL, with the method of your choosing. To instantiate a client, use the following:

  $client = static::createClient();

The following shows how to use the client to request a page - the developer list in the previous example. The client returns a crawler from its request method.

  $crawler = $client->request('GET', '/developers/list');

The crawler can be used to extract information from a response. It has methods to query the DOM using CSS selectors. For example, we can use the following to check that there are two table rows in the developer table. This assertion ensures that the exact number of developers in the database are output, meaning that if the assertion passes then all rows have been printed with no duplicates.

  $this->assertEquals(2, $crawler->filter('.developer-list tbody tr')->count());

We can also test more thoroughly the developers we expect to have been output. The following tests that the two developers in the database have their username printed in their row, as well as the rows being output in the correct order (ascending by username).

  $this->assertEquals(1, $crawler->filter('.developer-list tbody tr')->eq(0)->filter('td:contains("androidDev")')->count());
  $this->assertEquals(1, $crawler->filter('.developer-list tbody tr')->eq(1)->filter('td:contains("iosDev")')->count());

The complete test class looks like the following:

  namespace Codevate\DeveloperBundle\Tests;

  class DeveloperControllerTest extends WebTestCase
  {
    public function setUp()
    {
      parent::setUp();
    }

    public function testDeveloperListShowsAll()
    {
      // Load the developer list page
      $client = static::createClient();
      $crawler = $client->request('GET', '/developers/list');

      // Ensure there are two developer rows - no more, no less
      $this->assertEquals(2, $crawler->filter('.developer-list tbody tr')->count());

      // Ensure the two expected developers are present, and in the right order
      $this->assertEquals(1, $crawler->filter('.developer-list tbody tr')->eq(0)->filter('td:contains("androidDev")')->count());
      $this->assertEquals(1, $crawler->filter('.developer-list tbody tr')->eq(1)->filter('td:contains("iosDev")')->count());
    }
  }

This test extends the WebTestCase from the previous chapter, so that our fixtures are loaded before every test in the setUp method.

To run tests:

  $ phpunit -c app

Testing forms

Imagine we had an 'add developer' page which contained a form to add a new developer that looked like the following:

  <html>
    <head>
      <title>Add developer</title>
    </head>
    <body>
      <h1>Add developer</h1>
      <form method="post" action="/developers/add">
        <input type="text" name="username"/>
        <input type="text" name="bio"/>

        <input type="submit" id="add_developer_submit"/>
      </form>
    </body>
  </html>

Once this form is submitted, the new developer input should be added to the database and the user should be redirected back to the developer list. The newly added developer should then be visible in the developer list. The following test ensures that this is the actual behaviour:

  public function testAddDeveloper()
  {
    // Load the add developer form
    $client = static::createClient();
    $crawler = $client->request('GET', '/developers/add');

    // Find the submit button of the form
    $form = $crawler->filter('#add_developer_submit')->form();

    // Fill out and submit the form
    $crawler = $this->client->submit(
      $form,
      array(
        'username' => 'new-developer-username',
        'bio' => 'new-developer-bio'
      )
    );

    // Test that we're redirected back to the developer list, then follow the redirect
    $this->assertTrue($this->client->getResponse()->isRedirect('/developers/list'));
    $crawler = $this->client->followRedirect();

    // Test that the new developer has been added to the table
    $usernameTd = $crawler->filter('.developer-list tbody tr td:contains("new-developer-username")');
    $this->assertEquals(1, $usernameTd->count());
    $this->assertEquals(1, $usernameTd->siblings()->filter('td:contains("new-developer-bio")')->count());
  }

Symfony has a convenient way to submit forms in functional tests. Firstly, we have to find the submit button of the form using a CSS selector. Once we have the submit button, we can pass it and the form parameters to the client's submit method. We can also test that the redirect is working properly, using $this->client->getResponse()->isRedirect().


Now we know how to write basic functional tests in Symfony. In the next chapter we'll look at building unit tests using mocking, to test individual parts of applications in isolation.

Want more on this topic?

comments powered by Disqus

Let’s talk about how our software can work for you.

Get in touch