8. Test - Controllers

8.1 Overview

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. Instead of subclassing Mad_Test_Unit, they now subclass Mad_Test_Functional.

Functional tests can load fixtures using the fixtures() method just like our unit tests. In fact, Mad_Test_Functional itself subclasses Mad_Test_Unit.

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

Being a functional test gives us loads of features that we can use to test out that our controllers 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 become the user within our use case. They send a test request to the application and receive the response.

HTTP Request Simulation

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
{
    // a test
    public function setUp()
    {
        $this->request  = new Mad_Controller_Request_Mock();
        $this->response = new Mad_Controller_Response_Mock();
    }
}

8.2 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()
{
    // check the routing
    $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()
{
    // change cookies
    $this->request->setCookie('FOLDERID', '123');

    // change IP address
    $this->request->setRemoteIp('172.17.118.5');

    // simulate an ajax request
    $this->request->setIsAjax(true);

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

8.3 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');

    // this will print the HTML body of the index action
    // terminal so that you can take a look at it.
    print $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');

    // get the value that PAGE_ID var was set to using setVar() action
    $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));

    // get the value at BINDER_ID set by $this->cookie[] in the action
    $binderId = $this->getCookie('BINDER_ID');
}

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

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

    // get the value at FOLDER_ID set by $this->session[] in the action
    $folder = $this->getCookie('FOLDER_ID');
}

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

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

    // get the value at SUCCESS_MSG set by $this->flash[] in the action
    $msg = $this->getFlash('SUCCESS_MSG');
}

8.4 Functional Assertions

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

  • assertRouting()
  • assertNoRouting()
  • assertAction()
  • assertAssigns()
  • assertAssignsCookie()
  • assertAssignsSession()
  • assertAssignsFlash()
  • assertTemplate()
  • assertResponse()
  • assertRedirectedTo()
  • assertResponseContains()
  • assertResponseDoesNotContain()
  • assertTag()
  • assertNoTag()
  • assertSelect()

8.4.1 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');
}

8.4.2 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');

    // assert that this didn't match our route
    $this->assertNoRouting();
}

8.4.3 assertAction

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

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

    // assert binder() method in DocumentsController class was executed
    $this->assertAction('index', 'DocumentsController');
}

8.4.4 assertAssigns

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

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

    // make sure that the PAGE_ID template var was set to 'binderExplore'
    $this->assertAssigns('PAGE_ID', 'binderExplore');
}

8.4.5 assertAssignsCookie

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

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

    // make sure that the COOKIE_NAME cookie var was set to 'cookie value'
    $this->assertAssignsCookie('COOKIE_NAME', 'cookie value');
}

8.4.6 assertAssignsSession

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

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

    // assert SESSION_NAME session var was set to 'session value'
    $this->assertAssignsSession('SESSION_NAME', 'session value');
}

8.4.7 assertAssignsFlash

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

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

    // make sure that the FLASH_NAME flash var was set to 'cookie value'
    $this->assertAssignsFlash('FLASH_NAME', 'flash value');
}

8.4.8 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');
    // successful (200 OK) response
    $this->assertResponse('success');

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

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

    $this->get('moved_action');
    // 301 permanently moved by specific code
    $this->assertResponse(301);
}

8.4.9 assertRedirectedTo

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

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

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

8.4.10 assertResponseContains

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

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

    // make sure the string 'Documents' is in the response
    $this->assertResponseContains('Documents');

    // make sure the given regexp pattern is matched in the response
    $this->assertResponseContains('/[0-9]{1,} Documents/');
}

8.4.11 assertResponseDoesNotContain

assertResponseDoesNotContain(): does the exact opposite of assertResponseContains(). Makes sure that the given string/regexp is contained in the response body.

8.4.12 assertTag

assertTag(): assert that we find a HTML tag that matches the given options. This is by far the most complex assertion and has many options.

  • tag : the node type must match the corresponding value.
  • id : the node with the given id attribute must match the corresponsing value.
  • class : the node with the given class attribute must match the corresponsing value.
  • attributes : a hash. The node's attributres must match the corresponsing values in the hash.
  • content : The text content must match the given value.
  • parent : a hash. The node's parent must match the corresponsing hash.
  • child : a hash. At least one of the node's immediate children must meet the criteria described by the hash.
  • ancestor : a hash. At least one of the node's ancestors must meet the criteria described by the hash.
  • descendant : a hash. At least one of the node's descendants must meet the criteria described by the hash.
  • children : a hash, for counting children of a node.
    Accepts the keys:
    • count : a number which must equal the number of children that match
    • less_than : the number of matching children must be greater than this number
    • greater_than : the number of matching children must be less than this number
    • only : another hash consisting of the keys to use to match on the children, and only matching children will be counted

// assert there is a "span" tag
$this->assertTag(array('tag' => 'span'));

// assert there is a span element with an id="my_id"
$this->assertTag(array('tag' => 'span', 'id' => 'my_id'));

// assert there is a span element with an class="my_class"
$this->assertTag(array('tag' => 'span', 'id' => 'my_class'));

// assert there is a "span" tag with the content "Hello World"
$this->assertTag(array('tag'     => 'span',
                       'content' => 'Hello World'));

// assert there is a "span" tag with content matching the regexp pattern
$this->assertTag(array('tag'     => 'span',
                       'content' => '/Hello D(erek|allas)/'));

// assert there is a "span" with an "list" class attribute
$this->assertTag(array('tag' => 'span',
                       'attributes' => array('class' => 'list')));

// assert there is a "span" inside of a "div"
$this->assertTag(array('tag'    => 'span',
                       'parent' => array('tag' => 'div')));

// assert there is a "span" somewhere inside a "table"
$this->assertTag(array('tag'      => 'span',
                       'ascestor' => array('tag' => 'table')));

// assert there is a "span" with at least one "em" child
$this->assertTag(array('tag'   => 'span',
                       'child' => array('tag' => 'em')));

// assert there is a "span" containing a (possibly nested) "strong" tag.
$this->assertTag(array('tag'        => 'span',
                       'descendant' => array('tag' => 'strong')));

// assert there is a "span" containing 5-10 "em" tags as immediate children
$this->assertTag(array(
        'tag'       => 'span',
        'children'  => array('less_than'    => 11,
                             'greater_than' => 4,
                             'only'         => array('tag' => 'em'))));


// get funky: assert there is a "div", with an "ul" ancestor and a
// "li" parent (with class="enum"), and containing a "span" descendant
// that contains element with id="my_test" and the text Hello World..
// phew!
$this->assertTag(array(
        'tag'      => 'div',
        'ancestor' => array('tag' => 'ul'),
        'parent'   => array('tag'        => 'li,
                            'attributes' => array('class' => 'enum')),
        'descendant' => array('tag'   => 'span',
                              'child' => array('id' => 'my_test',
                                               'content' => 'Hello World'))));

8.4.13 assertNoTag

assertNoTag(): does the exact opposite of assertTag(). Makes sure that an HTML tag is not found with the given options.

8.4.15 assertSelect

assertSelect(): assert that we find an HTML tag that matches the given CSS selector and options. This assertion provides a simple interface to the power of the assertTag method, and is probably the assertion 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
  • div > span : a span element which is a direct child of a div element

We can also do combinations of these options such as :
div#folder.open a.class_name
or
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));