TRAJOIN is an Application to Translate symfony documents Jointly.

home > 1.2/cookbook/en > test-application.txt

[1] Edit ↑TOP

How to test an application?


[2] Edit ↑TOP

The symfony framework has always been bundled with a functional testing framework and it is certainly one of its main strengths.


[3] Edit ↑TOP

What is a functional test? Functional tests goal is to test the integration of all your application layers: from the routing to the controller, templates, and database calls. They do not replace unit tests.


[4] Edit ↑TOP

The only thing it cannot test easily is the JavaScript code embedded in your templates. You can of course use a tool like selenium for this. But the good news is that the functional test framework can test "some" JavaScript code like Ajax calls.


[5] Edit ↑TOP

To do its job, the functional testing framework simulates a browser. It does not need a web server as it knows symfony internals and how to generate a response based on a request. This allows easy and deep introspection of the state of your application after each request. You can of course introspect the symfony core objects like the response or the user session, but also your own code like the model.


[6] Edit ↑TOP

Each release of symfony makes the functional testing framework even better. Today, I will show you all the goodness we have added for the symfony 1.2 version. Be prepared to be amazed!


[7] Edit ↑TOP

Decoupling


[8] Edit ↑TOP

Everybody knows that I like testing very much. I also like to refactor old code to make it better. For symfony 1.2, I have refactored the browser (sfBrowser) and the test browser (sfTestBrowser) classes to make them much more flexible and configurable.


[9] Edit ↑TOP

As of symfony 1.2, the functional testing framework is made of several distinct and reusable layers.


[10] Edit ↑TOP

The biggest changes is the introduction of testers. Testers are objects that knows how to test a specific layer of your application. Symfony comes with several built-in testers for the request, the response, the user, the view cache, forms, and Propel.


[11] Edit ↑TOP

A less important change is the introduction of the sfTestFunctional class, which relies on a sfBrowser object to test your application and manages all the registered testers.


[12] Edit ↑TOP

Here is a typical functional test:


[13] Edit ↑TOP

$browser = new sfTestFunctional(new sfBrowser());

$browser->
  get('/')->
  // do some tests
;

[14] Edit ↑TOP

To retain backward compatibility with symfony 1.0 and 1.1, you can still use the now deprecated sfTestBrowser class:


[15] Edit ↑TOP

$browser = new sfTestBrowser();

$browser->
  get('/')->
  // do some tests
;

[16] Edit ↑TOP

Testers


[17] Edit ↑TOP

So, all the testing is actually done by tester classes. A tester knows how to test a specific part of your application.


[18] Edit ↑TOP

The testers replace all the methods you are used to, like checkResponseElement() or isRequestParameter(). Of course, these methods are still available to retain backward compatibility (the UPGRADE_TO_1_2 file contains a table that references all the old methods and their tester equivalent).


[19] Edit ↑TOP

Here is a simple example that demonstrates how to replace isRequestParameter() calls by using the request tester:


[20] Edit ↑TOP

// before symfony 1.2
$browser->
  get('/')->

  isRequestParameter('module', 'foo')->
  checkResponseElement('h1', 'foo')
;

// as of symfony 1.2
$browser->
  get('/')->

  with('request')->isParameter('module', 'foo')->
  checkResponseElement('h1', 'foo')
;

[21] Edit ↑TOP

The with('request') call switches the context of the fluent interface to the request tester object for the very next call. So, the isParameter() method is a sfTesterRequest method.


[22] Edit ↑TOP

You can also create a block of calls in which the context is the tester object:


[23] Edit ↑TOP

$browser->
  get('/')->

  with('request')->begin()->
    isParameter('module', 'foo')->
    isParameter('action', 'index')->
  end()->

  checkResponseElement('h1', 'foo')
;

[24] Edit ↑TOP

All the method calls between begin() and end() are called against the current tester object.


[25] Edit ↑TOP

Let's see the testing methods provided by the built-in tester classes.


[26] Edit ↑TOP

Request Tester


[27] Edit ↑TOP

The request tester is defined in the sfTesterRequest class and contains the following methods:


[28] Edit ↑TOP
Method Description
isParameter Tests a request parameter
isMethod Tests the request method
isFormat Tests the request format
hasCookie Tests if the request has a given cookie
isCookie Tests the value of a cookie

[29] Edit ↑TOP

$browser->
  get('/')->

  with('request')->begin()->
    isParameter('module', 'foo')->
    isMethod('get')->
    isFormat('html')->
    hasCookie('foo')->
    isCookie('foo', 'bar')->
  end()
;

[30] Edit ↑TOP

Response Tester


[31] Edit ↑TOP

The response tester is defined in the sfTesterResponse class and contains the following methods:


[32] Edit ↑TOP
Method Description
isStatusCode Tests the response status code
contains Tests the response content with a simple regular expression
isHeader Tests the value of a given header
checkElement Checks the value of a CSS3 selector

[33] Edit ↑TOP

$browser->
  get('/')->

  with('response')->begin()->
    isStatusCode(200)->
    contains('foo')->
    isHeader('Content-Type', 'text/plain')->
    checkElement('ul.foo li:last', '/foo/')->
  end()
;

[34] Edit ↑TOP

View cache tester


[35] Edit ↑TOP

The view cache tester is defined in the sfTesterViewCache class and contains the following methods:


[36] Edit ↑TOP
Method Description
isCached Checks if a page/action is in the cache
isUriCached Checks if a specific URI (can be a partial) is in cache

[37] Edit ↑TOP

$browser->
  get('/')->

  with('view_cache')->begin()->
    isCached(true)->
    isUriCached('@sf_cache_partial?module=foo&action=_partial&sf_cache_key=some_cache_key')->
  end()
;

[38] Edit ↑TOP

User tester


[39] Edit ↑TOP

The user tester is defined in the sfTesterUser class and contains the following methods:


[40] Edit ↑TOP
Method Description
isCulture Tests the culture of the user
isAuthenticated Checks that the user is authenticated
hasCredential Checks for a user credential
isAttribute Tests the value of a given attribute
isFlash Tests the value of a flash variable

[41] Edit ↑TOP

$browser->
  get('/')->

  with('user')->begin()->
    isCulture('fr')->
    isAuthenticated(true)->
    hasCredential('admin')->
    isAttribute('sfguard_user_id', '3')->
    isFlash('notice', '/foo/')->
  end()
;

[42] Edit ↑TOP

Form Tester


[43] Edit ↑TOP

Time to discover some new sexy testers!


[44] Edit ↑TOP

The form tester is defined in sfTesterForm class. It knows if a form has been used in the previous request, has a reference to the form object itself, and allows you to introspect it.


[45] Edit ↑TOP
Method Description
hasErrors Checks if the submitted form has some error
isError Tests the value of an error for a given field
hasGlobalError Same as isError but for global errors

[46] Edit ↑TOP

The isError() method takes the same kind of second argument as the checkResponseElement() method.


[47] Edit ↑TOP

$browser->
  click('save', array(...))->
  with('form')->begin()->
    hasErrors()->
    hasGlobalError('The login and password does not match.')->
    isError('name', 'Required.')->
    isError('name', '/Required/')->
    isError('name', '!/Invalid/')->
    isError('name')->
    isError('name', false)->
    isError('name', 1)->
  end()
;

[48] Edit ↑TOP

Propel Tester


[49] Edit ↑TOP

Here is another great tester: the propel tester.


[50] Edit ↑TOP

It does not replace HTML response checks but is a mean to also check things that are not displayed in the browser but nonetheless important to test (for example if the last_connection timestamp for a user has been updated, or if the number of views for an article has been incremented, ...).


[51] Edit ↑TOP

The propel tester is defined in sfTesterPropel in the Propel plugin and must be registered before being used:


[52] Edit ↑TOP

$browser->setTester('propel', 'sfTesterPropel');

[53] Edit ↑TOP

After the tester is registered, you can use it in your tests:


[54] Edit ↑TOP

$browser->
  post('/')->
  with('propel')->begin()->
    check('Article', array('title' => 'foo'), false)->
    check('Article', array('title' => '!foo'), false)->
    check('Article', array(), 4)->
    check('Article', array('title' => '%foo%'), true)->
    check('Article', array('title' => '!%foo%'))->
    check('Article', $criteria)->
  end()
;

[55] Edit ↑TOP

The propel tester only provides one method: check(). This method behaves differently based on the arguments you pass to it:


[56] Edit ↑TOP
  • The first argument is the model class name
  • The second one is a Criteria object or a simple array of conditions
  • The third one can be:
    • true to check that some objects match the conditions
    • false to check that no object matches the conditions
    • or an integer to check the number of matching objects

[57] Edit ↑TOP

Extend or create a tester


[58] Edit ↑TOP

Using testers have severals advantages:


[59] Edit ↑TOP
  • Isolation: Thanks to the decoupling of the testers, we provide many more testing methods than before.
  • Readability: Your tests are much more readable, thanks to the block concept and shorter method names.
  • Extensibility: You can extend each tester with your very own methods or create your own tester class.

[60] Edit ↑TOP

Extend a built-in tester


[61] Edit ↑TOP

If you want to add some methods to an existing tester, you need to create a class that inherits from the built-in tester and re-register it with your own class name:


[62] Edit ↑TOP

class ApplicationTesterRequest extends sfTesterRequest
{
  // add some tester methods
}

// in your functional tests
$browser->setTester('request', 'ApplicationTesterRequest');

[63] Edit ↑TOP

If you need to override a bunch of built-in testers, you can use the setTesters method:


[64] Edit ↑TOP

$browser->setTesters(array(
  'request'  => 'ApplicationTesterRequest',
  'response' => 'ApplicationTesterResponse',
));

[65] Edit ↑TOP

A tester method can do anything you like but must always end with the following code for the fluent interface to work correctly:


[66] Edit ↑TOP

return $this->getObjectToReturn();

[67] Edit ↑TOP

In your method, you have access to several objects:


[68] Edit ↑TOP
  • $this->browser: The current browser object
  • $this->tester: The lime_test object

[69] Edit ↑TOP

Create a new tester


[70] Edit ↑TOP

You can also create new tester class by registering it with a unique name:


[71] Edit ↑TOP

$browser->setTester('my_tester', 'myTester');

[72] Edit ↑TOP

A tester class inherits from sfTester and must implement the following methods:


[73] Edit ↑TOP
  • initialize(): This method is called every time you are using with() in your tests. This is useful to get some object after the request has been sent:

    
    public function initialize()
    {
      $this->request = $this->browser->getRequest();
    }
    

[74] Edit ↑TOP
  • prepare(): This method is called just before any call to the browser object. This is useful if you need to do something just before the request is send.

[75] Edit ↑TOP

Be fluent


[76] Edit ↑TOP

When you write a lot of functional tests for a given module, it is sometimes useful to have some visual information about what it is being done. The new testers adds a new level of indentation and make tests more readable.


[77] Edit ↑TOP

Also, there is a new info() method that outputs some text to help categorize your tests:


[78] Edit ↑TOP

$browser->
  info('First scenario: Form with errors')->
  // ... some tests
  info('Second scenario: Valid form submission')->
  // ... some more tests
;

[79] Edit ↑TOP

info in the browser


[80] Edit ↑TOP

Debugging tests


[81] Edit ↑TOP

When a problem occurs in a functional test, the HTML transfered to the browser help diagnose the cause. As of symfony 1.2, this is quite easy to display the generated HTML without interrupting the fluent interface style:


[82] Edit ↑TOP

$browser->
  get('/a_uri_with_an_error')->
  with('response')->debug()->
  // some tests that won't be executed
;

[83] Edit ↑TOP

The debug() method will output the response headers and content and will interrupt the flow of the browser.


[84] Edit ↑TOP

The same debug() method exists for the form tester and outputs the submitted values and the form errors if any:


[85] Edit ↑TOP

$browser->
  post('/post_to_a_form_with_some_errors')->
  with('form')->debug()->
  // some tests that won't be executed
;

[86] Edit ↑TOP

debug


[87] Edit ↑TOP

That's all for today. It has never been easier to test your symfony applications. So, I hope the new testing framework will convince you that it is not that hard and that it can save your day.


[88] Edit ↑TOP

As for the new web debug toolbar panels, if you create new testers, don't hesitate to package them as a plugin.


Comments

Menu

Documentation



Latest Histories

Untranslated