<com:TContent ID="body">
<h1>More complete ProjectTestCase</h1>
<p>
In creating a new project, we need to check the following:
<ul>
	<li>that the project does not already exists, i.e. no duplicate project name</li>
	<li>that the project creator and project manager exists when a new project is created</li>
</ul>
So to perform this task, we will modified the test code. 
<com:TTextHighlighter Language="php" CssClass="source">
function testProjectDaoCanCreateNewProject()
{
	$project = new Project();
	$project->ID = 1;
	$project->Name = "Project 1";
	$project->CreatorUserName = "Customer A";
	$project->ManagerUserName = "Manager A";

	$customer = new TimeTrackerUser();
	$customer->ID = 1;
	$customer->Name = "Customer A";
	
	$manager = new TimeTrackerUser();
	$manager->ID = 2;
	$manager->Name = "Manager A";
	
	if(($conn = $this->connection) instanceof MockTSqlMapper)
	{
		//return the customer and manager
		$conn->setReturnValue('queryForObject', 
				$customer, array('GetUserByName', 'Customer A'));
		$conn->setReturnValue('queryForObject', 
				$manager, array('GetUserByName', 'Manager A'));
		
		//project does not exist
		$conn->setReturnValue('queryForObject', 
				null, array('GetProjectByName', 'Project 1'));
		
		$param['project'] = $project;
		$param['creator'] = $customer->ID;
		$param['manager'] = $manager->ID; 
		
		$conn->setReturnValue('insert', true, 
				array('CreateNewProject', $param));
		$conn->setReturnReference('queryForObject', 
				$project, array('GetProjectByID', 1));
				
		//we expect queryForObject to be called 3 times
		$conn->expectMinimumCallCount('queryForObject', 3);
		$conn->expectAtLeastOnce('insert');
	}

	$this->assertTrue($this->dao->createNewProject($project));		
	$this->assertEqual($this->dao->getProjectByID(1), $project);
}
</com:TTextHighlighter>
</p>
<p>It may seem very weird that there is so much code in the tests
and why we even bother to write all these test codes. Well, using the
above test code we have the following advantages.</p>
<div class="info"><b class="tip">Advantages of Mock</b>
<ol>
	<li>we don't need a real database base connection to test the code, 
	this means we can start relying on tested code ealier</li>
	<li>when a test fails we know that problem is not part of the database</li> 
	<li>when a test fail, we can quickly pin point the problem</li>
	<li>the test suite gives us the confidence to refactor our code</li>
</ol>
</div>

<p>Of couse, the test will not be able to cover the higher interactions, such as
the user interface, so intergration or functional web test will be used later on.
</p>

<p>So how did we come up with the above tests? We started simple, then we
ask what sort of things it should handle. We assume that the connection object
work as expect or known to be unexpected and see how the method we want to test handle
these situations.</p>

<p>If we run the above test, we will be faced with numerous errors. First will be
that the <tt>TimeTrackerUser</tt> can not be found.</p>

<h1>Creating a new User Class</h1>
<p>Notice that the <tt>Project</tt> class contains <tt>CreatorUserName</tt> 
and <tt>ManagerUserName</tt> properties. So at some point we
are going to need at least one <tt>User</tt> class. We shall name the class as
<tt>TimeTrackerUser</tt> and save it as <tt>APP_CODE/TimeTrackerUser.php</tt>
<com:TTextHighlighter Language="php" CssClass="source">
&lt;?php
Prado::using('System.Security.TUser');
Prado::using('System.Security.TUserManager');
class TimeTrackerUser extends TUser
{
	private $_ID;
	
	public function __construct()
	{
		parent::__construct(new TUserManager());
	}
		
	public function getID(){ return $this->_ID; }
	
	public function setID($value)
	{ 
		if(is_null($this->_ID))
			$this->_ID = $value;
		else
			throw new TimeTrackerUserException(
				'timetracker_user_readonly_id');
	}
}
?&gt;
</com:TTextHighlighter>

<h1>Custom Exceptions</h1>
<p>We enforce that the ID of the user to be read-only once it has been
set by throwing a custom exception. Prado's exception classes
uses a string key to find a localized exception string containing more
detailed description of the exception. The default exception messages
are stored in the <tt>framework/Exceptions/messages.txt</tt>. This
file location can be changed by overriding the <tt>getErrorMessageFile()</tt>
method of <tt>TException</tt> class. We define a custom exception class
for all Time Tracker application exceptions as <tt>TimeTrackerException</tt>
and save the class as <tt>APP_CODE/TimeTrackerException.php</tt>.</p>

<com:TTextHighlighter Language="php" CssClass="source">
&lt;?php
class TimeTrackerException extends TException
{
	/**
	 * @return string path to the error message file
	 */
	protected function getErrorMessageFile()
	{
        return dirname(__FILE__).'/exceptions.txt';
	}	
}
?&gt;
</com:TTextHighlighter>

<p>We then create a <tt>exceptions.txt</tt> file in the <tt>APP_CODE</tt>
directory with the following content.</p>

<com:TTextHighlighter Language="text" CssClass="source">
timetracker_user_readonly_id 	= Time tracker user ID is read-only.
</com:TTextHighlighter>

<p>Additional parameters passed in the exception constructor can be
added the message string using <tt>{0}</tt> as the first additional parameter,
and <tt>{1}</tt> as the second additional parameter, and so on.
For example, suppose we want to raise the follow exception.
</p> 

<com:TTextHighlighter Language="php" CssClass="source">
throw new TimeTrackerException('project_exists', $projectName);
</com:TTextHighlighter>

<p>The exception error message in <tt>exceptions.txt</tt> may contain something like:</p>
<com:TTextHighlighter Language="text" CssClass="source">
project_exists 	= Time tracker project '{0}' already exists.
</com:TTextHighlighter>

<h1>Completing the test case</h1>
<p>From the unit test code, we can pretty much see what the implementation
for <tt>createNewProject()</tt> will look like.</p>

<com:TTextHighlighter Language="php" CssClass="source">
public function createNewProject($project)
{
	$sqlmap = $this->getConnection();
	$creator = $sqlmap->queryForObject('GetUserByName', $project->CreatorUserName);
	$manager = $sqlmap->queryForObject('GetUserByName', $project->ManagerUserName);
	$exists = $sqlmap->queryForObject('GetProjectByName', $project->Name);
	if($exists)
	{
		throw new TimeTrackerException(
				'project_exists', $project->Name);
	}
	else if(!$creator || !$manager)
	{
		throw new TimeTrackerException(
				'invalid_creator_and_manager',
				$project->Name, $project->CreatorUserName, 
				$project->ManagerUserName); 
	}
	else
	{
		$param['project'] = $project;
		$param['creator'] = $creator->ID;
		$param['manager'] = $manager->ID; 
		return $sqlmap->insert('CreateNewProject', $param);         
	}
}
</com:TTextHighlighter>

<div class="tip"><b class="tip">Tip:</b>
A hierachy of exception class can be used to have fine exception handling.
Since this is a small project and for simplicity, we shall use the application level
<tt>TimeTrackerException</tt> exception class for most exception cases. 
</div>

</com:TContent>