IngmarBoddington
6/15/2014 - 1:02 PM

Basic PHPUnit Notes

Basic PHPUnit Notes

A few gard won lessons!

1. You cannot set expectations on a non-mocked method
2. Don't use multiple mocks of the same class and then inject these into another mock
3. When using at, this applies at an object level, not method
4. When using at, any mocked methods will be expected in the ordering


phpunit
  - Standard use (consumes phpunit.xml.dist)

phpunit --coverage-text
  - Given coverage info in cli
  
phpunit --coverage-html <directory>
  - As above but creates coverage report in provided directory

phpunit -c <file>
  - Use given config file

Structure
============

class RemoteConnectTest extends PHPUnit_Framework_TestCase
	- Makes test methods available

public function setUp(){ }
	- Will be run before every test

public function tearDown(){ }
	- Will be run after every test

Assertions
===============
(all using $this-><method>)

- GENERAL
	
assertInstanceOf(<constraint>, <actual>)
	- Passes if actual is of type constraint
	
assertEquals(<constraint>, <actual>
	- Passes if actual is equal to constraint

assertClassHasStaticAttribute(<constraint>, <class>)
	- Passes if class has static attribute with name constraint
	
assertFileExists(<file>)
	- Passes if file exists
	
expectOutputString(<string>)
	- Pass if method echos / prints string
	
- ARRAY

assertArrayHasKey(<constraint>, <actual>)
	- Passes if actual array has element with key constraint
	
- BOOL

assertTrue(<actual>)
	- Passes if actual evaluates to true
	
assertFalse(<actual>)
	- Passes if actual evaluates to false

- INT

assertGreaterThan(<constraint>, <actual>)
	- Passes if actual is greater than constraint
	
assertLessThan(<constraint>, <actual>)
	- Passes if actual is less than constraint

Mocks
=====

$this->getMock
	- Make class have dependancy injected
	- This can than be replaced with mock
	- Set to return values for called methods

Other Functions
===============
(all using $this-><method>)

fail(<message>)
	- Signal test failure with message
	
	
Annotations
===========
(Set in doc block above test method)

@expectedException <exceptionType>
	- Passes if method throws exception of given type
	
	
General Notes
=============
- One assertion per test?
- Allows refactoring to be re-tested to ensure to functionality not broken
- TDD = Write test, write code that passes test, repeat in small iterative cycles
- Promoted "keep it simple stupid" (KISS) and "You aren't gonna need it" (YAGNI)


Installation Through Pear
=========================
Install Pear
	wget http://pear.php.net/go-pear.phar
	sudo php -d detect_unicode=0 go-pear.phar
Install PHPUnit
	pear channel-discover pear.phpunit.de
	pear install pear.phpunit.de/PHPUnit --alldeps
(Remember to add pear to PATH var)


Installation Through Composer
=============================
(There is no offical version of PHPUnit on packagist, use unofficial)
Install Composer (locally)
	curl -sS https://getcomposer.org/installer | php
	(move to bin dir (e.g. /usr/bin) to make composer command globally available)
Installing PHPUnit for project
	Create JSON file in root (composer.json) Example (with optional php v require):
	{
		"require" : {
			"php" : "version",
			"EHER/PHPUnit": "1.6"
		}
	}
	composer install
	
	
PHPUnit converts errors into exceptions
=======================================
- Can use something like the below code to tesk for errors instead
- Sets customer error handler which stored error details in array
- Defined assertion that an error has occured by checking the array
  
<?php
class MyTest extends PHPUnit_Framework_TestCase
{
    private $errors;
 
    protected function setUp() {
        $this->errors = array();
        set_error_handler(array($this, "errorHandler"));
    }
 
    public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
        $this->errors[] = compact("errno", "errstr", "errfile",
            "errline", "errcontext");
    }
 
    public function assertError($errstr, $errno) {
        foreach ($this->errors as $error) {
            if ($error["errstr"] === $errstr
                && $error["errno"] === $errno) {
                return;
            }
        }
        $this->fail("Error with level " . $errno .
            " and message '" . $errstr . "' not found in ", 
            var_export($this->errors, TRUE));
    }
 
    public function testDoStuff() {
        // execute code that triggers a warning
        $this->assertError("Message for the expected error",
            E_USER_WARNING);
    }
}

Tips
=======
Passing an array as subsequest returns on a mocked method:
->will(new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($returnValueArray));