Functional Testing

Now that we have our models tested, we can go to some higher level tests. We call the code that tests our controllers functional tests, and they are stored in tests/functional.

Functional tests follow all the same conventions that unit tests do and can load fixtures using the fixtures() method. Functional tests subclass Mad_Test_Functional, which itself subclasses Md_Test_Unit:

class DocumentsControllerTest extends Mad_Test_Functional
{
    public function testIndex()
    {
    }
}

Functional tests have many features that we can use to test that our controllers and views are working properly. We want to test aspects like:

  • Was the request routed to the correct controller?
  • Was the web request successful?
  • Were we redirected to the right page?
  • Were the correct cookies/session data set?
  • Was the correct template(s) rendered?
  • Was the template rendered correctly?

The Functional tests can send a test request to the application and receive the response:

../_images/requests.gif

This requires our tests to have both the request and response objects defined in our setUp() method. This is already generated within the stub file when creating new controllers with script/generate:

class DocumentsControllerTest extends Mad_Test_Functional
{
    public function setUp()
    {
        $this->request  = new Mad_Controller_Request_Mock();
        $this->response = new Mad_Controller_Response_Mock();
    }
}

Performing Requests

Requests can be simulated using few different methods.

get() performs a simulated HTTP GET request to an action of the controller:

public function testIndexPage2()
{
    $this->get('index', array('page' => '2'));
}

In the example above, a GET request is made to the index action of the controller being tested. The params are also set for page 2.

post() performs a simulated HTTP POST request to an action of the controller:

public function testEditSavesDocument()
{
    $this->post('edit', array('id' => '123'));
}

In the example above, a POST request is made to the edit action of the controller being tested. The params are also set for ID 2.

followRedirect() will do an HTTP GET on the redirect location in a 300 level response:

public function testSomething()
{
  // index action performs redirectTo(array('action' => 'binder'));
  $this->get('index');

  // this will perform another GET to follow the redirect
  $this->followRedirect();
}

recognize() doesn’t actually perform a request, but will evaluate the routing and instantiate the correct controller for the reqeuest. This is a good way to test out that routing data is working correctly:

public function testSomething()
{
    $this->recognize('/explore/folder/123');
}

Before a request is made, you can modify the request data to your heart’s content. Some things that will probably commonly be changed are: Cookies, Session Data, Flash Data, etc. This can help set the environment for your test:

public function testSomething()
{
    $this->request->setCookie('FOLDERID', '123');
    $this->request->setRemoteIp('172.17.118.5');
    $this->request->setIsAjax(true);

    $this->get('/explore/folder');
}

Available Data

After a request has been performed, we have access to a valid response object. Most of the time assertions done on the response will be done through our custom assertion methods mentioned below, but one handy option you have for debugging your tests is to print out the response body:

public function testIndexBody()
{
    $this->get('index');

    echo $this->response;
}

We also have access to some new data that we can use to assert that the correct actions have been executed during the request.

getAssigns() will get us any template variable that was set during the action:

public function testIndexAssignsPageId()
{
    $this->get('index');

    $pageId = $this->getAssigns('PAGE_ID');
}

getCookie() will get us any cookie data set during the action:

public function testShowSetsDocumentIdCookie()
{
    $this->get('show', array('id' => 3));

    $binderId = $this->getCookie('BINDER_ID');
}

getSession() will get us any session data set during the action:

public function testShowSetsSelectedFolderInSession()
{
    $this->get('show');

    $folder = $this->getCookie('FOLDER_ID');
}

getFlash() will get us any flash data set during the action:

public function testLoginFlashesSuccessMessage()
{
    $this->get('login');

    $msg = $this->getFlash('SUCCESS_MSG');
}

Assertions

There are many assertions available to the functional tests. These will help evaluate that the request/response gets executed correctly.

AssertRouting

assertRouting(): asserts that the URL given to recognize() set the given params correctly:

public function testSomething()
{
    $this->recognize('/explore/binder/123');

    // assert that the params['id'] was set correctly from the url
    $this->assertRouting('id' => '123');
}

AssertNoRouting

assertNoRouting(): does the exact opposite of assertRouting(). It makes sure that the URL given to recognize() was not routed correctly:

public function testSomething()
{
    $this->recognize('/explore/binder/asdf');

    $this->assertNoRouting();
}

AssertAction

assertAction(): asserts that the given action/controller were executed during the request:

public function testSomething()
{
    $this->recognize('index');

    $this->assertAction('index', 'DocumentsController');
}

AssertAssigns

assertAssigns(): asserts that the given variable was assigned to the given value:

public function testSomething()
{
    $this->get('binder', array('id' => 123));

    $this->assertAssigns('PAGE_ID', 'binderExplore');
}

AssertAssignsCookie

assertAssignsCookie(): asserts that the given cookie was assigned to the given value:

public function testSomething()
{
    $this->get('binder', array('id' => 123));

    $this->assertAssignsCookie('COOKIE_NAME', 'cookie value');
}

AssertAssignsSession

assertAssignsSession(): asserts that the given session was assigned to the given value:

public function testSomething()
{
    $this->get('binder', array('id' => '123');

    $this->assertAssignsSession('SESSION_NAME', 'session value');
}

AssertAssignsSession

assertAssignsFlash(): asserts that the given flash was assigned to the given value:

public function testSomething()
{
    $this->get('binder', array('id' => '123');

    $this->assertAssignsFlash('FLASH_NAME', 'flash value');
}

AssertResponse

assertResponse(): asserts that the response was successful, redirected, missing, an error, or a specific HTTP code:

public function testSomething()
{
    $this->get('binder', array('id' => '123');
    $this->assertResponse('success');

    $this->get('index');
    $this->assertResponse('redirect'); // 302

    $this->get('missing_action');
    $this->assertResponse('missing'); // 404

    $this->get('moved_action');
    $this->assertResponse(301); // 301
}

AssertRedirectedTo

assertRedirectedTo(): assert that the response is a redirect to the given URL:

public function testSomething()
{
    $this->get('index');

    $this->assertRedirectedTo(array('action' => 'binder'));
}

AssertResponseContains

assertResponseContains(): assert that the given string/regexp is contained in the response body:

public function testSomething()
{
    $this->get('index');

    $this->assertResponseContains('Documents');

    $this->assertResponseContains('/[0-9]{1,} Documents/');
}

Another assertion, assertResponseDoesNotContain(), asserts that the response does not contain the content.

AssertSelect

assertSelect(): assert that we find an HTML tag that matches the given CSS selector and options. This assertion is probably the one you’ll use the most during your testing.

The syntax of assertSelect is very simple if you know CSS selector syntax:

  • div : an element of type div
  • div.warning : an element of type div whose class is “warning”
  • div#myid : an element of type div whose ID equal to “myid”
  • div[foo="bar"] : an element of type div whose “foo” attribute value is exactly equal to “bar”
  • div[foo~="bar"] : an element of type div whose “foo” attribute value is a list of space-separated values, one of which is exactly equal to “bar”
  • div[foo*="bar"] : an element of type div whose “foo” attribute value contains the substring “bar”
  • div span : an span element descendant of a div element</li>
  • div > span : a span element which is a direct child of a div element</li>
We can also do combinations of these options such as:
  • div#folder.open a.class_name
  • a[href="http://example.com"][title="example"].selected.big > span

The second argument determines what we’re matching in the content or number of tags. It can be one 4 options:

  • content : match the content of the tag
  • true/false : match if the tag exists/doesn’t exist
  • number : match a specific number of elements
  • range : to match a range of elements, we can use an array with the options ‘>’ and ‘<’

There is an element with the id “binder_1” with the content “Test Foo”:

$this->assertSelect('#binder_1', "Test Foo");

There is not an element with the id “binder_1” and the content “Test Foo”:

$this->assertSelect('#binder_1', "Test Foo", false);

The “#binder_foo” id exists:

$this->assertSelect('#binder_foo');
$this->assertSelect('#binder_foo', true);

The “#binder_foo” id DOES NOT exist:

$this->assertSelect('#binder_foo', false);

There are 10 div elements with the class folder:

$this->assertSelect('div.folder', 10);

There are more than 2, less than 10 li elements:

$this->assertSelect('ul > li', array('>' => 2, '<' => 10));