sfPhpunit plugin
==============

The `sfPhpunitPlugin` is a symfony plugin that creates stub classes for [PHPUnit](http://www.phpunit.de/) of classes in lib/model (and other directories). Unit testing and functional testing are supported. 

Symfony provides a testing framework, but many times it is necessary to use an existing PHPUnit installation. This plugin helps
creating the files necessary for PHPUnit to work with symfony classes like Propel/Doctrine extended classes.
Since 1.0.5a1 can manage namespaces (folders) in lib/model.

sfPhpunitPlugin does not create test methods for methods on the base classes of the generated model. It only creates test methods for the
custom methods added by the developer in the classes in lib/model or any other folders.

sfPhpunitPlugin also creates an AllPhpunitTests.php class file that can be used by PHPUnit to run all tests. This created file will include the creation
of the configuration object with the provided application name and create a connection to the database so propel based objects can be tested.

Migration
---------
  * From 1.0.4 to 1.0.5

    __Warning:__
    
    Many things had to be changed in this version, which conflict with older versions and test classes.
    
    We suggest these steps:
      * Backup your tests
      * Move your tests out of your test folder
      * Rebuild tests for your models
      
                $ symfony phpunit:create application
                
      * Copy your test code in the according new generated files (Note: all new tests will be saved into test/phpunit/<subfolder>, old path: test/<subfolder>)
 
            
Requirements
--------------------
PHPUnit has to be [installed](http://www.phpunit.de/manual/current/en/installation.html) on the test machine and it has to be available with the **phpunit** command from command line.

Installation
--------------------

  * Install the plugin

        $ symfony plugin:install sfPhpunitPlugin
        
  * Enable Plugin (Only for Symfony 1.2 and above)

   Modify config/ProjectConfiguration.class.php
   
        [php]
          public function setup()
          {
            // for compatibility / remove and enable only the plugins you want
            $this->enableAllPluginsExcept('sfDoctrinePlugin');
        
            // or
            
        	$this->enablePlugins(array('sfPropelPlugin', 'sfPhpunitPlugin'));
        	$this->disablePlugins(array('sfDoctrinePlugin'));
        
          }        
         
Creation of unit tests
--------------------

  * Create all model tests (Propel)
  
        $ symfony phpunit:create application
        
    This command will go over the lib/model directory and for each .php file it will create a fileTest.php in test/phpunit/unit/model/.
    
    It will also create a test/phpunit/AllPhpunitTests.php that can be used to run all tests with *symfony phpunit:testall* or *phpunit test/phpunit/AllPhpunitTests.php*.
    
    The default database connection will be `propel`. You can change it by adding -c myConn

  * Run all tests         
  
        $ symfony phpunit:testall
        
   This is similar to running phpunit test/phpunit/AllPhpunitTests.php
   
  * **option type**: Create all model tests of another type (Doctrine)
  
        $ symfony phpunit:create application --type=doctrine
        
    This command will go over the lib/model/doctrine directory and for each .php file it will create a fileTest.php in test/phpunit/unit/model/.
    
    It will also create a test/phpunit/AllPhpunitTests.php that can be used to run all tests with *symfony phpunit:testall* or *phpunit test/phpunit/AllPhpunitTests.php*.
    
    The default database connection will be `doctrine`. You can change it by adding -c myConn
   
  * **option model_path**: Create tests for additional/other model directories
  
        $ symfony phpunit:create application --model_path=apps/admin/lib

  * **option model**: Create tests for a single model class
  
        $ symfony phpunit:create application --model=ClassName

  * **options class, class_path, file_suffix**: Generate a test class for a custom class
  
        $ symfony phpunit:create --class=myClass --class_path="lib/subfolder" --file_suffix=".class.php" application
    
    Unlike in the former examples you have here the possibility to point to the custom folder of your class and to define the file suffix/extension directly.
       
  * **options overwrite, overwrite_alltests, overwrite_base_test**: If you want to overwrite your existing classes during file generation use the overwrite option (overwrites your test class and the bootstrap file). If you want to overwrite the base class and the AllTests file you may use the options *overwrite_alltests*, *overwrite_base_test*. Existing classes are not overwritten by default.        
  
        $ symfony phpunit:create --overwrite --overwrite_alltests, --overwrite_base_test --class=myClass --class_path="lib/subfolder" --file_suffix=".class.php" application
         
  * Help of all arguments and options
  
        $ symfony help phpunit:create

Common usage
--------------------
PHPUnit uses __setUp__ and __tearDown__ methods for doing stuff just before and after a test is called.
In this plugin you may not have to overwrite those methods, but **_start** and **_end**.
With this approach you do not have to call the parent methods, if you have to overwrite it (the same approach like
the configure/setup methods in the forms system).  

Usage of unit tests
--------------------
Unit tests do not create a sfContext instance from start-up. The base unit test class offers however a possibility to
create one, without generating it on your own.

        $this->getContext();
        
This will create a sfContext instance, if no valid instance does exist already.
Just add this call in your **_start** method and you can use the context from there on.

        protected function _start()
        {
        	$this->getContext();
        	$this->o = new SomeClass();
        }

Creation of functional tests (new in 1.0.5)
--------------------
Creating and running unit tests with PHPUnit is quite simple, because the test workflow does not belong to any other symfony class.
But what about functional tests? The functional tests are using the sfTestFunctional class (sfTestBrowser in former times),
 which is coupled to the lime_test class.
For this reason, the 1.0.5 version introduces the new sfPhpunitTest class, which overwrites the normal lime_test class and maps all test methods to
the nested PHPUnit test case (This approach could be better realized via a testing interface, but symfony 1.2 does not support one).
This means, that the lime class is still loaded, but its original methods are never called.

Here an example of the normal ok method of the lime class ($this->testCase is the actual functional test case):

    [php]
    public function ok($exp, $message = '')
    {
    	$result = (bool) $exp;
    	$this->testCase->assertTrue($result, $message);
    	return $result;
    }

This approach changes nothing for the usage of the main functional test code, as you may see in the next two listings.
The first one is the new provided possibiliy with PHPUnit and the second one is the normal way with lime.

Functional test with PHPUnit:

    [php]
    class fooActionsTest extends BasePhpunitFunctionalTestCase
    {
    
    	public function test1()
    	{
    		$browser = $this->getBrowser();
		
    		$browser->
    		get('/foo/index')->
    
    		with('request')->begin()->
    		isParameter('module', 'foo')->
    		isParameter('action', 'index')->
    		end()->

    		with('response')->begin()->
    		isStatusCode(200)->
    		checkElement('body', '!/This is a temporary page/')->
    		end()
    		;
    	}
    }

Functional test with lime:

    [php]
    $browser = new sfTestFunctional(new sfBrowser());

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

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

      with('response')->begin()->
        isStatusCode(200)->
        checkElement('body', '!/This is a temporary page/')->
      end()
    ;

The only difference is the way one has to fetch the browser instance, afterwards the code is equivalent.



Customize sfPhpunitPlugin generated class files
-----------------------------------------------

By default, `sfPhpunitPlugin` comes with a set of templates for generating the stub class files.
These templates are located inside the data plugin directory and can be customized:

  * `alltests.tpl` - Template for generating AllPhpunitTests.php file.
  * `functional/BasePhpunitFunctionalTestCase.tpl` - Template for the base _functional_ test case.
  * `functional/PhpunitFunctionalTestCase.tpl` - Template for generating the individual _functional_ test class files.
  * `unit/file_table.tpl` - Template for generating the individual _unit_ test class files for Doctrine table classes.
  * `unit/file.tpl` - Template for generating the individual _unit_ test class files.
  * `unit/method.tpl` - Template for each test method inside the _unit_ test class file.
  * `unit/BasePhpunitTestCase.tpl` - Template for the base _unit_ test case.
  
For customizing one of those templates, the only thing you have to do is, to copy the according plugin template to your project data dir __data/sfPhpunitPlugin__.
The tasks will automatically look if there does exist any custom template. If no custom template is found, the default one within the plugin will be taken.

Example:

    plugins/sfPhpunitPlugin/data/unit/file.tpl --> project/data/sfPhpunitPlugin/unit/file.tpl

Note, that the subfolder structure has to be adopted.  


Execution of unit tests
--------------------
There is no task for execution a single phpunit test. Calling the phpunit test class directly with the phpunit command will do the job.

    [php]
    # unit test
    phpunit test/phpunit/unit/SomeTest.php
    
    # functional test
    phpunit test/phpunit/functional/service/feedActionsTest.php
    
    # all tests
    phpunit test/phpunit/AllPhpunitTests.php

    # or    
    php symfony phpunit:testall
    
    
  
Hints
==============
You like the ANSI colors of lime? No problem, PHPUnit has this functionality too and provides it as option.

    [php]
    phpunit --colors test/phpunit/AllPhpunitTests.php

__Contact us for any other requirements/ideas!__

Plugin tests
==============
This plugin is currently bundled with two lime unit tests, which are testing the generation of the skeleton files.

__Warning__:
If you want to run those tests within your project, please save and backup everything in your test/phpunit folder, thus these tests are
writing into this folder.

More code snippets
==============
    
Example of a unit test class file
--------

    [php]
    <?php
    
    require_once sfConfig::get('sf_test_dir').'/phpunit/BasePhpunitTestCase.class.php';
    
    /**
     * sfPhpunitPluginTestClassTest
     */
    class sfPhpunitPluginTestClassTest extends BasePhpunitTestCase
    {
    	/**
    	* sfPhpunitPluginTestClass
    	*
    	* @var sfPhpunitPluginTestClass
    	*/
    	protected $o;
    
    	protected function _start()
    	{
    		$this->o = new sfPhpunitPluginTestClass();
    	}
    
    	public function testSetFoo()
    	{
    		$this->markTestIncomplete(
    			'This test has not been implemented yet.'
    		);
    	}    

    	public function testGetFoo()
    	{
    		$this->markTestIncomplete(
    			'This test has not been implemented yet.'
    		);
    	}    

    	protected function _end()
    	{
    	}

    }
    
Example of a functional test class file
--------
    [php]
    <?php
    require_once dirname(__FILE__).'/../../BasePhpunitFunctionalTestCase.class.php';

    class fooActionsTest extends BasePhpunitFunctionalTestCase
    {
    
    	public function getApplication()
    	{
    		return 'service';
    	}

    	public function getEnvironment()
    	{
    		return 'test';
    	}

    	public function test1()
    	{
    		$browser = $this->getBrowser();

    		$browser->
    		get('/foo/index')->
    
    		with('request')->begin()->
    		isParameter('module', 'foo')->
    		isParameter('action', 'index')->
    		end()->

    		with('response')->begin()->
    		isStatusCode(200)->
    		checkElement('body', '!/This is a temporary page/')->
    		end()
    		;
    	}
    }