How to made the code coverage to 100% - php

Recently I started to write PHPUnit test. And this is my model code.(I used CodeIgniter 3).
Account_model.php
class Account_model extends CI_Model
{
...
public function select_by_seq($seq = '', $select_columns = [])
{
try {
$bind = [':a_seq' => $seq];
// check $select_colums is exist in table
if ($this->check_column($select_columns) === false)
{
throw new Exception('columns illegal', 201);
}
...
$sql = "select ....
from {$this->db->dbprefix('account')}
where a_seq = :a_seq";
$query = $this->db->query($sql, $bind);
// ===== this always not runing. =====
if ($query === false)
{
// ===== this always not runing. =====
throw new Exception('sql errors', 301);
}
else
{
return $query->result_array();
}
}
catch (Exception $error)
{
// set error log
$this->set_error_log($error->getCode() . $error->getMessage());
}
return false;
}
}
This is my test Account_model_test.php
class Account_model_test extends TestCase
{
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
$CI =& get_instance();
}
public function setUp()
{
$this->resetInstance();
loader('model', 'account_model');
$this->obj = $this->CI->account_model;
}
public function test_select_by_seq()
{
$result = $this->obj->select_by_seq(
$seq = '20160830'
);
$this->assertCount(1, $result);
}
public function test_select_by_seq_with_illegal_column()
{
$result = $this->obj->select_by_seq(
$seq = '20160830',
$select_columns = ['illegal']
);
$this->assertFalse($result);
}
...
}
Because I write SQL by myself. I founded my PHPUnit test can't cover this if ($query === false). And then my code coverage didn't achieve 100%. This problem let me think the 100% is very important for the unit test? Or I had to modify my model code? Thanks your help.

It's good to always have code coverage 100%. But like you said there are situations when this is very hard to achieve. So having code coverage greater than 70% is quite good for most of the projects. See this link regarding the minimum code coverage required in project. But you should try to extract your business logic to its own class (Repository) and unit test it.

It would be good if you show us your test file also, so we can be more clear about what you already have there.
Without knowing that, what I can suggest is to use Mock of query function, so it can return false as result. You can find more about Mocks here
Also as written before: you should not focus on 100% code coverage.

The #codeCoverageIgnore, #codeCoverageIgnoreStart and #codeCoverageIgnoreEnd annotations can be used to exclude lines of code from the coverage analysis. -- PHPUnit Manual
There are examples in the PHPUnit documentation.
In a nutshell, wrap the code to be ignored by code coverage in *Start *End annotations:
if (false) {
// #codeCoverageIgnoreStart
print '*';
// #codeCoverageIgnoreEnd
}
Also see What is a reasonable code coverage % for unit tests (and why)?.

Related

PHPunit method expected to be called 1 time, actually called 0 times

I've been stuck on this for a while and I'm not sure why PHPunit can't see that the function is being called.
This is the code I'm trying to test:
public function handle()
{
$path = $this->request->getPath();
$requestMethod = $this->request->getMethod();
if (!$path) {
$this->redirect('home');
} else if (!$this->isMethodPathFound($path, $requestMethod)) {
$this->redirect('404');
} else {
$handler = $this->getControllerFullName($this->routes[$path]['handler']);
if (is_callable($handler)) {
call_user_func($handler);
} else {
$this->redirect('404');
}
}
}
/**
* #param string $path
* #param int $statusCode
*/
public function redirect($path, $statusCode = 303)
{
if (defined('TESTING_ENVIRONMENT') && TESTING_ENVIRONMENT) {
return;
}
header(
'Location: ' . $this->request->getProtocol() .
$this->request->getHost() . '/' . $path,
true,
$statusCode
);
die();
}
The TESTING_ENVIRONMENT variable is set for the header function so it does not trigger on running PHPunit (I don't want to create another class to have that redirect function just to be able to mock it for one test) and this is the testing code:
public function testHandlePathIsEmpty()
{
$requestMock = $this->getMockBuilder('\services\Request')->getMock();
$requestMock->expects($this->once())->method('getPath')->willReturn('');
$requestMock->expects($this->once())->method('getMethod')->willReturn('GET');
$routerMock = $this->getMockBuilder('\services\Router')
->setConstructorArgs([$this->routes, $requestMock])
->enableProxyingToOriginalMethods()
->getMock();
$routerMock->expects($this->once())->method('redirect')
->with('asdasd')->willReturn(true);
$routerMock->handle();
}
The $routerMock object should definitely invoke the "redirect" function, and it says that it does not get invoked..even though when I var_dump/die inside the function, it does go inside of it.
Thanks for the help!
Though you hesitated to show the complete output of phpunit's error, your problem is very likely not that your method is not called, but that it is not called with all the expectations you defined.
Your code
$routerMock->expects($this->once())->method('redirect')
->with('asdasd')->willReturn(true);
translates to the following expectations: The method redirect must be called exactly once with an argument 'asdasd' and will return true.
From your testcode I do not see that there is asdasd passed to the redirect method. Your test will most likely succeed when you remove the with expectation.
Just to make this clear. If you have to mock the class u want to test, your code is way to complex and you should think about implementing your logic in another way.
How about not mocking the class you are actually testing, create the new instance by passing the Request and a Router Mock (Router mock might not have any logic since you are not going to use it) and then do the following in your code:
public function handle()
{
$request = $this->request;
$path = $request->getPath();
if (!$path) {
$this->redirect('home');
} else if (!$this->isMethodPathFound($path, $request->getMethod())) {
$this->redirect('404');
} else {
$handler = $this->getControllerFullName($this->routes[$path]['handler']);
if (is_callable($handler)) {
call_user_func($handler);
} else {
$this->redirect('404');
}
}
}
In your Unit-Test, you now can just test for
$requestMock
->expects($this->never())
->method('getMethod');
I see that this would only cover the second case to not being executed but the third one could happen aswell. Thats always a point why your code is not clean enough.
You should read something about KISS and SOLID to make your code more testable. This method is just too complex as you could test it correctly.

How do you mock a virtual binary file so that exec() / system() / passthru() function output can be tested?

I have an interesting problem and have searched the internet, but haven't yet found an answer.
I work for a company that doesn't allow it's workers to utilize OOP, it is kind of ridiculous, but the working experience is valuable.
Consider the following function:
function get_setting_values_from_file( $parameter )
{
exec("/usr/var/binary --options $parameter", $output, $return);
$settings = file( $output[0] );
foreach( $settings as $setting ) {
if( strstr( $setting, "color") ) {
$setting = explode( ":", $setting );
return $setting[1];
}
}
return false;
}
I need to unit test a similar function. I am currently using phpUnit for my tests and the vfsStream libraries to mock the file system, but how do you mock the call to exec("/usr/var/binary --options $parameter", $output, $return) when I'm developing with no access to the actual system? What is the recommend approach for dealing with test cases like this?
All feedback is appreciated.
You could mock exec() by using a function mock library. I made one (php-mock) for you which requires you to use namespaces
namespace foo;
use phpmock\phpunit\PHPMock;
class ExecTest extends \PHPUnit_Framework_TestCase
{
use PHPMock;
public function testExec()
{
$mock = $this->getFunctionMock(__NAMESPACE__, "exec");
$mock->expects($this->once())->willReturnCallback(
function ($command, &$output, &$return_var) {
$this->assertEquals("foo", $command);
$output = "failure";
$return_var = 1;
}
);
exec("foo", $output, $return_var);
$this->assertEquals("failure", $output);
$this->assertEquals(1, $return_var);
}
}
Simply mock this function to return the text that you are trying to get into $settings. You do not need to call the executable, simply create the file or return.
For instance, assuming the function get_setting_values_from_file() returns the settings as an array, you can simply mock the function in your test to return the settings as an array. Create a test stub to mock the object that contains the get_setting_values_from_file() method, and have that mock simply return the same FALSE, 1 or 2 that the test assumed.
$stub = $this->getMock('GetSettingsClass');
$stub->expects($this->any())
->method('get_settings_from_file')
->will($this->returnValue(0));
This is from the PHPUnit manual -> http://phpunit.de/manual/3.8/en/test-doubles.html#test-doubles.stubs
Optionally, you could even bypass the call, and simply test the functions/code that works on the returns by creating the array and passing it to those functions.
Assumed Example in the main code:
...
$settings = get_setting_values_from_file( 'UserType' );
$UserType = get_user_type($settings);
return $UserType;
function get_user_type($settings)
{
if($settings !== FALSE) // Returned from your function if parameter is not found
{
switch($settings)
{
case 1:
return 'User'; // Best to use Constants, but for example here only
break;
case 2:
return 'Admin';
break;
...
}
}
else
{
return FALSE;
}
}
Now, in your test, you can simply
$this->assertFalse(get_user_type(FALSE, 'Ensure not found data is handled properly as FALSE is returned');
$this->assertEqual('User', get_user_type(1), 'Test UserType=1');
$this->assertEqual('Admin', get_user_type(1), 'Test UserType=2');
...
These work as the code does not call the function that had to mock the read from the OS, but does handle all the expected returns by calling the function processing the setting return value. Here, you have simply assumed the return from the function 'get_setting_values_from_file()' without needing the file or any mocks.
This does NOT however test reading from the file, which I would do in another test by using the setUp and tearDown to actual create a file with the values you want (fopen/fwrite) and then call your function and ensure it returns what is expected.
I hope this helps to explain what I was thinking.

Advice/suggestions for my first project PHP Classes

Any advice is welcome!
I have a very limited understanding of php classes but below is my starting point for the route I would like to take. The code is a reflection of what I see in my head and how I would like to go about business. Does my code even look ok, or am I way off base?
What are your thoughts, how would you go about achieving such a task as form->validate->insertquery->sendmail->return messages and errors?
Please try and keep your answers simple enough for me to digest as for me its about understanding whats going on and not just a copy/paste job.
Kindest regards,
Phil.
Note: This is a base structure only, no complete code added.
<?php
//=======================================
//class.logging.php
//========================================
class logging
{
public $data = array();
public $errors = array();
function __construct()
{
array_pop($_POST);
$this->data =($this->_logging)? is_isset(filterStr($_POST) : '';
foreach($this->data as $key=> $value)
{
$this->data[$key] = $value;
}
//print_r($this->data); de-bugging
}
public function is_isset($str)
{
if(isset($str)) ? true: false;
}
public function filterStr($str)
{
return preg_match(do somthing, $str);
}
public function validate_post()
{
try
{
if(!is_numeric($data['cardID'])) ? throw new Exception('CardID must be numeric!') : continue;
}
catch (Exception $e)
{
return $errors = $e->getCode();
}
}
public function showErrors()
{
foreach($errors as $error => $err)
{
print('<div class="notok"></div><br />');
}
}
public function insertQ()
{
$query = "";
}
}
//=======================================
//Usercp.php
//========================================
if(isset($_GET['mode']))
{
$mode = $_GET['mode'];
}
else
{
$mode = 'usercp';
}
switch($mode)
{
case 'usercp':
echo 'Welcome to the User Control Panel';
break;
case 'logging':
require_once 'class.logging.php';
$logger = new logging();
if(isset($_POST['submit'])
{
if($logger->validate_post === true)
{
$logger->insertQ();
require_once '/scripts/PHPMailer/class.phpmailer.php';
$mailer = new PHPMailer();
$mailer->PHPMailer();
}
else
{
echo ''.$logger->showErrors.'';
}
}
else
{
echo
'
<form action="'.$_SERVER['PHP_SELF'].'?mode=logging" method="post">
</form>
';
}
break;
case 'user_logout':
// do somthing
break;
case 'user_settings':
// do somthing
break;
?>
I have decided to use this method for returning errors rather than print them in the method, thanks for the advice Igor!
catch (Exception $e)
{
$this->errors[] = $e->getMessage();
#ERROR DE_BUGGING ONLY================================
#print('<pre>');
#print_r($this->errors);
#print('</pre>');
#=====================================================
}
if($this->errors)
{
return false;
}
else
{
return true;
}
It looks like you have a decent understanding of OOP code. I see declared public vars and even try/catches, though I'd say don't forget the "public" visibility keyword in front of "function __construct()"—not absolutely necessary, but it keeps with good coding practices.
Further, I would say that everything you are doing here has been written, debugged, and fixed, and proven production worthy already by each of the dozens of PHP frameworks out there. The specific task you mentioned, "form->validate->insertquery->sendmail->return messages and errors" is so incredibly easy with Zend Framework, my framework of choice. And I would imagine the same is true for Symphony, Solar, Cake, etc.
Do yourself a favor and stop coding what has been coded already. Learn a framework that has a community, regular updates, and well-written thorough documentation. Again, I recommend Zend Framework.
First advice that comes to mind: Separate logic from presentation. You can start by using some template engine like Smarty. If you will keep it all mixed up, soon it will be a huge dump.
Also try to include class definitions from separate files, and as a next step I would recommend adopting some pattern like Model-View-Controller to separate models from logic.
That's what I can think of without digging too deep into the code.

Is it possible to make an object return false by default?

I tried to ask this before, and messed up the question, so I'll try again. Is it possible to make an object return false by default when put in an if statement? What I want:
$dog = new DogObject();
if($dog)
{
return "This is bad;"
}
else
{
return "Excellent! $dog was false!"
}
Is there a way this is possible? It's not completely necessary, but would save me some lines of code. thanks!
No, PHP has no support for operator overloading. Maybe they'll add it in a future version.
Use the instanceof keyword.
For example
$result = Users->insertNewUser();
if($result instanceof MyErrorClass){
(CHECK WHAT WENT WRONG AND SAY WHY)
} else {
//Go on about our business because everything worked.
}
Info is here.
Use this? Not a real neat solution, but does what you want:
<?php
class Foo
{
private $valid = false;
public function Bar ( )
{
// Do stuff
}
public function __toString ( )
{
return ( $this -> valid ) ? '1' : '0';
}
}
?>
Zero is considered false, one is considered true by PHP
I was attempting to do this myself and found a solution that appears to work.
In response to the others who were trying to answer the question by telling the asker to use a different solution, I will also try to explain the reason for the question. Neither the original poster or I want to use an exception, because the point is not to use exception handling features and put that burden on any code we use this class in. The point, at least for me, was to be able to use this class seamlessly in other PHP code that may be written in a non-object-oriented or non-exception-based style. Many built-in PHP functions are written in such a way that a result of false for unsuccessful processes is desirable. At the same time, we might want to be able to handle this object in a special way in our own code.
For example, we might want to do something like:
if ( !($goodObject = ObjectFactory::getObject($objectType)) ) {
// if $objectType was not something ObjectFactory could handle, it
// might return a Special Case object such as FalseObject below
// (see Patterns of Enterprise Application Architecture)
// in order to indicate something went wrong.
// (Because it is easy to do it this way.)
//
// FalseObject could have methods for displaying error information.
}
Here's a very simple implementation.
class FalseObject {
public function __toString() {
// return an empty string that in PHP evaluates to false
return '';
}
}
$false = new FalseObject();
if ( $false ) {
print $false . ' is false.';
} else {
print $false . ' is true.';
}
print '<br />';
if ( !$false ) {
print $false . ' is really true.';
} else {
print $false . ' is really false.';
}
// I am printing $false just to make sure nothing unexpected is happening.
The output is:
is false.
is really false.
I've tested this and it works even if you have some declared variables inside the class, such as:
class FalseObject {
const flag = true;
public $message = 'a message';
public function __toString() {
return '';
}
}
A slightly more interesting implementation might be:
class FalseException extends Exception {
final public function __toString() {
return '';
}
}
class CustomException extends FalseException { }
$false = new CustomException('Something went wrong.');
Using the same test code as before, $false evaluates to false.
I recently had to do something similar, using the null object pattern. Unfortunately, the null object was returning true and the variable in question was sometimes an actual null value (from the function's default parameter). The best way I came up with was if((string)$var) { although this wouldn't work for empty arrays.
Putting something in "an if statement" is simply evaluating the variable there as a boolean.
In your example, $dog would need to be always false for that to work. There is no way to tell when your variable is about to be evaluated in a boolean expression.
What is your ultimate purpose here? What lines of code are you trying to save?
I'm not sure about the object itself. Possible. You could try something like, add a public property to the DogObject class and then have that set by default to false. Such as.
class DogObject
{
var $isValid = false;
public function IsValid()
{
return $isValid;
}
}
And then when you would instantiate it, it would be false by default.
$dog = new DogObject();
if($dog->IsValid())
{
return "This is bad;"
}
else
{
return "Excellent! $dog was false!"
}
Just a thought.
If I understand what your asking, I think you want to do this:
if (!$dog){
return "$dog was false";
}
The ! means not. SO you could read that, "If not dog, or if dog is NOT true"
Under what conditions do you want if($dog) to evaluate to false? You can't do what you've literally asked for, but perhaps the conditioned could be replaced by something that does what you want.
class UserController
{
public function newuserAction()
{
$userModel = new UserModel();
if ($userModel->insertUser()) {
// Success!
} else {
die($userModel->getError());
}
}
}
Or
class UserController
{
public function newuserAction()
{
$userModel = new UserModel();
try {
$userModel->insertUser()
}
catch (Exception $e) {
die($e);
}
}
}
There are a million ways to handle errors. It all depends on the complexity of the error and the amount of recovery options.
How about using an Implicit Cast Operator like the following C# ?
like so:
class DogObject
{
public static implicit operator bool(DogObject a)
{
return false;
}
}
Then you can go...
var dog = new DogObject();
if(!dog)
{
Console.WriteLine("dog was false");
}

How do I write unit tests in PHP? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I've read everywhere about how great they are, but for some reason I can't seem to figure out how exactly I'm supposed to test something. Could someone perhaps post a piece of example code and how they would test it? If it's not too much trouble :)
There is a 3rd "framework", which is by far easier to learn - even easier than SimpleTest, it's called phpt.
A primer can be found here:
http://qa.php.net/write-test.php
Edit: Just saw your request for sample code.
Let's assume you have the following function in a file called lib.php:
<?php
function foo($bar)
{
return $bar;
}
?>
Really simple and straight forward, the parameter you pass in, is returned. So let's look at a test for this function, we'll call the test file foo.phpt:
--TEST--
foo() function - A basic test to see if it works. :)
--FILE--
<?php
include 'lib.php'; // might need to adjust path if not in the same dir
$bar = 'Hello World';
var_dump(foo($bar));
?>
--EXPECT--
string(11) "Hello World"
In a nutshell, we provide the parameter $bar with value "Hello World" and we var_dump() the response of the function call to foo().
To run this test, use: pear run-test path/to/foo.phpt
This requires a working install of PEAR on your system, which is pretty common in most circumstances. If you need to install it, I recommend to install the latest version available. In case you need help to set it up, feel free to ask (but provide OS, etc.).
There are two frameworks you can use for unit testing. Simpletest and PHPUnit, which I prefer. Read the tutorials on how to write and run tests on the homepage of PHPUnit. It is quite easy and well described.
You can make unit testing more effective by changing your coding style to accommodate it.
I recommend browsing the Google Testing Blog, in particular the post on Writing Testable Code.
I rolled my own because i didnt have time to learn someone elses way of doing things, this took about 20 minutes to write up, 10 to adapt it for posting here.
Unittesting is very usefull to me.
this is kinda long but it explains itself and there is an example at the bottom.
/**
* Provides Assertions
**/
class Assert
{
public static function AreEqual( $a, $b )
{
if ( $a != $b )
{
throw new Exception( 'Subjects are not equal.' );
}
}
}
/**
* Provides a loggable entity with information on a test and how it executed
**/
class TestResult
{
protected $_testableInstance = null;
protected $_isSuccess = false;
public function getSuccess()
{
return $this->_isSuccess;
}
protected $_output = '';
public function getOutput()
{
return $_output;
}
public function setOutput( $value )
{
$_output = $value;
}
protected $_test = null;
public function getTest()
{
return $this->_test;
}
public function getName()
{
return $this->_test->getName();
}
public function getComment()
{
return $this->ParseComment( $this->_test->getDocComment() );
}
private function ParseComment( $comment )
{
$lines = explode( "\n", $comment );
for( $i = 0; $i < count( $lines ); $i ++ )
{
$lines[$i] = trim( $lines[ $i ] );
}
return implode( "\n", $lines );
}
protected $_exception = null;
public function getException()
{
return $this->_exception;
}
static public function CreateFailure( Testable $object, ReflectionMethod $test, Exception $exception )
{
$result = new self();
$result->_isSuccess = false;
$result->testableInstance = $object;
$result->_test = $test;
$result->_exception = $exception;
return $result;
}
static public function CreateSuccess( Testable $object, ReflectionMethod $test )
{
$result = new self();
$result->_isSuccess = true;
$result->testableInstance = $object;
$result->_test = $test;
return $result;
}
}
/**
* Provides a base class to derive tests from
**/
abstract class Testable
{
protected $test_log = array();
/**
* Logs the result of a test. keeps track of results for later inspection, Overridable to log elsewhere.
**/
protected function Log( TestResult $result )
{
$this->test_log[] = $result;
printf( "Test: %s was a %s %s\n"
,$result->getName()
,$result->getSuccess() ? 'success' : 'failure'
,$result->getSuccess() ? '' : sprintf( "\n%s (lines:%d-%d; file:%s)"
,$result->getComment()
,$result->getTest()->getStartLine()
,$result->getTest()->getEndLine()
,$result->getTest()->getFileName()
)
);
}
final public function RunTests()
{
$class = new ReflectionClass( $this );
foreach( $class->GetMethods() as $method )
{
$methodname = $method->getName();
if ( strlen( $methodname ) > 4 && substr( $methodname, 0, 4 ) == 'Test' )
{
ob_start();
try
{
$this->$methodname();
$result = TestResult::CreateSuccess( $this, $method );
}
catch( Exception $ex )
{
$result = TestResult::CreateFailure( $this, $method, $ex );
}
$output = ob_get_clean();
$result->setOutput( $output );
$this->Log( $result );
}
}
}
}
/**
* a simple Test suite with two tests
**/
class MyTest extends Testable
{
/**
* This test is designed to fail
**/
public function TestOne()
{
Assert::AreEqual( 1, 2 );
}
/**
* This test is designed to succeed
**/
public function TestTwo()
{
Assert::AreEqual( 1, 1 );
}
}
// this is how to use it.
$test = new MyTest();
$test->RunTests();
This outputs:
Test: TestOne was a failure
/**
* This test is designed to fail
**/ (lines:149-152; file:/Users/kris/Desktop/Testable.php)
Test: TestTwo was a success
Get PHPUnit. It is very easy to use.
Then start with very simple assertions. You can do alot with AssertEquals before you get into anything else. That's a good way to get your feet wet.
You may also want to try writing your test first (since you gave your question the TDD tag) and then write your code. If you haven't done this before it is an eye-opener.
require_once 'ClassYouWantToTest';
require_once 'PHPUnit...blah,blah,whatever';
class ClassYouWantToTest extends PHPUnit...blah,blah,whatever
{
private $ClassYouWantToTest;
protected function setUp ()
{
parent::setUp();
$this->ClassYouWantToTest = new ClassYouWantToTest(/* parameters */);
}
protected function tearDown ()
{
$this->ClassYouWantToTest = null;
parent::tearDown();
}
public function __construct ()
{
// not really needed
}
/**
* Tests ClassYouWantToTest->methodFoo()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodFoo('putValueOfParamHere), 'expectedOutputHere);
/**
* Tests ClassYouWantToTest->methodBar()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodBar('putValueOfParamHere), 'expectedOutputHere);
}
For simple tests AND documentation, php-doctest is quite nice and it's a really easy way to get started since you don't have to open a separate file. Imagine the function below:
/**
* Sums 2 numbers
* <code>
* //doctest: add
* echo add(5,2);
* //expects:
* 7
* </code>
*/
function add($a,$b){
return $a + $b;
}
If you now run this file through phpdt (command-line runner of php-doctest) 1 test will be run. The doctest is contained inside the < code > block. Doctest originated in python and is fine for giving useful & runnable examples on how the code is supposed to work. You can't use it exclusively because the code itself would litter up with test cases but I've found that it's useful alongside a more formal tdd library - i use phpunit.
This 1st answer here sums it up nicely (it's not unit vs doctest ).
phpunit is pretty much the defacto unit testing framework for php. there is also DocTest (available as a PEAR package) and a few others.
php itself is tested for regressions and the like via phpt tests which can also be run via pear.
Codeception tests are much like common unit tests but are much powerful in things where you need mocking and stubbing.
Here is the sample controller test. Notice how easily stubs are created. How easily you check the method was invoked.
<?php
use Codeception\Util\Stub as Stub;
const VALID_USER_ID = 1;
const INVALID_USER_ID = 0;
class UserControllerCest {
public $class = 'UserController';
public function show(CodeGuy $I) {
// prepare environment
$I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
$I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null ))); };
$I->setProperty($controller, 'db', $db);
$I->executeTestedMethodOn($controller, VALID_USER_ID)
->seeResultEquals(true)
->seeMethodInvoked($controller, 'render');
$I->expect('it will render 404 page for non existent user')
->executeTestedMethodOn($controller, INVALID_USER_ID)
->seeResultNotEquals(true)
->seeMethodInvoked($controller, 'render404','User not found')
->seeMethodNotInvoked($controller, 'render');
}
}
Also there are other cool things. You can test database state, filesystem, etc.
Besides the excellent suggestions about test frameworks already given, are you building your application with one of the PHP web frameworks that has automated testing built in, such as Symfony or CakePHP? Sometimes having a place to just drop in your test methods reduces the start-up friction some people associate with automated testing and TDD.
Way too much to re-post here, but here is a great article on using phpt. It covers a number of aspects around phpt that are often overlooked, so it could be worth a read to expand your knowledge of php beyond just writing a test. Fortunately the article also discusses writing tests!
The main points of discussion
Discover how marginally documented aspects of PHP work (or pretty much any part for that matter)
Write simple unit tests for your own PHP code
Write tests as part of an extension or to convey a potential bug to the internals or QA groups
I know there is a lot of info here already, but since this still shows up on Google searches i might as well add Chinook Test Suite to the list. It is a simple and small test framework.
You can easily test your classes with it and also create mock objects. You run the tests through a web browser and (not yet) through a console.
In the browser you can specify what test class or even what test method to run. Or you can simply run all tests.
A screenshot from the github page:
What i like about it is the way you assert tests. This is done with so called "fluent assertions". Example:
$this->Assert($datetime)->Should()->BeAfter($someDatetime);
And creating mock objects is a breeze too (with a fluent like syntax):
$mock = new CFMock::Create(new DummyClass());
$mock->ACallTo('SomeMethod')->Returns('some value');
Anyway, more info can be found on the github page with a code example as well:
https://github.com/w00/Chinook-TestSuite

Categories