sven
4/4/2014 - 2:59 PM

Testing with PHPUnit

Testing with PHPUnit

Testing with PHPUnit

Improved Error Handling and Styling

Workflow

  1. Given
  2. When
  3. Then

Usage

  • Run tests: phpunit aka vendor/bin/phpunit
  • Version: phpunit --version
  • Run testsuite only: phpunit --testsuite testsuitename
  • Run method only: phpunit --filter myTestMethod
  • Run group only: phpunit --group groupname
  • Generate coverage report: phpunit --coverage-html tests/coverage

PHPUnit & PhpStorm

External lib. include path (OS X & Mamp): /Applications/MAMP/Library/bin

PHPUnit & Laravel

PHPUnit & VVV

  • Defined version of PHPUnit: /usr/local/src/composer/composer.json
  • Update: sudo composer update

PHPUnit & WordPress

PHPUnit & WP-CLI


Notes

  • Every test-method has to be public and a method name starting with test or have the @test notation
  • Mock: Does not trigger the actual class, calls the interface instead (set expectation on what should happen)
  • Spy: Triggers the actual class, verify that it happened afterwards (reverse mock: do the action, verify that it happened)
  • Stubs: Returns any kind of hard-coded data (pre-defined values)
  • Dummies: Adheres to the contract just to run the test (methods will return NULL)
  • Factory: reusable element for test-related setup (for creating other objects)
  • Fixture: also a reusable element for test-related setup
  • A test-related method (called fixture) can be a private method not starting with test
  • Method visibility can be switched from protected to public via a proxy class
  • Skip tests via $this->markTestSkipped( 'Must be revisited.' )
  • Coverage report requires Xdebug extension
  • Tests coverage can be documented with @covers notation

Laravel Notes

  • Disable exception handling for a test: $this->withoutExceptionHandling()
  • Act as signed-in user: $this->actingAs(factory('App\User')->create());

Laravel Mockery Testing Helpers

Given App\Project class:

class Project {
    public function subscribeTo($name, $user) {
        //
    }
}

Mocking the object

// Create user
$user = factory(User::class)->create();

// Create mock
$this->mock(Project::class, function ($mock) use ($user) {
    $mock->shouldReceive()->subscribeTo('members', $user)->once();
});

// Perform action
$this->actingAs($user)->get('/endpoint');

Spying the object

// Create user
$user = factory(User::class)->create();

// Create spy
$spy = $this->spy(Project::class);

// Perform action
$this->actingAs($user)->get('/endpoint');

// Confirm action
$spy->shouldHaveReceived()->subscribeTo('members', $user);
# PHPUnit completion
source ~/phpunit.bash
# Bash-Completion script for PHPUnit
#
# Created by Henrique Moody <henriquemoody@gmail.com>
# https://gist.github.com/henriquemoody/5014805
#
_phpunit()
{
    COMPREPLY=()

    local cur="${COMP_WORDS[COMP_CWORD]}"
    local prev="${COMP_WORDS[COMP_CWORD-1]}"
    local opts="--coverage-clover --coverage-crap4j --coverage-html --coverage-php --coverage-text --coverage-xml --log-junit --log-tap --log-json --testdox-html --testdox-text --filter --testsuite --group --exclude-group --list-groups --test-suffix --report-useless-tests --strict-coverage --disallow-test-output --enforce-time-limit --disallow-todo-tests --process-isolation --no-globals-backup --static-backup --colors --columns --columns --stderr --stop-on-error --stop-on-failure --stop-on-risky --stop-on-skipped --stop-on-incomplete -v --verbose --debug --loader --repeat --tap --testdox --printer --bootstrap -c --configuration --no-configuration --include-path -d -h --help --version"
    local diropts="--coverage-html|--coverage-xml|--include-path"
    local nocompleteopts="--filter|--testsuite|--group|--exclude-group|--test-suffix|--loader|--repeat|--printer|-d|-h|--help|--version"

    if [[ ${prev} =~ ${diropts} ]]; then
        COMPREPLY=( $(compgen -d -- ${cur}) )
        return 0

    elif [[ ${prev} =~ ${nocompleteopts} ]]; then
        return 0

    elif [[ ${prev} = --columns ]]; then
        COMPREPLY=( $(compgen -W "max" -- ${cur}) )
        return 0

    elif [[ ${prev} = --colors ]]; then
        COMPREPLY=( $(compgen -W "auto never always" -- ${cur}) )
        return 0

    elif [[ "${cur}" == -* ]]; then
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0

    else
        COMPREPLY=( $(compgen -f -- "${cur}") )
        return 0
    fi
}

complete -F _phpunit phpunit
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
	bootstrap="./tests/bootstrap.php"
	backupGlobals="false"
	colors="true"
	convertErrorsToExceptions="true"
	convertNoticesToExceptions="true"
	convertWarningsToExceptions="true"
>
	<php>
		<const name="PluginNamespace\Tests\DB_NAME" value="PLACEHOLDER"/>
		<const name="PluginNamespace\Tests\DB_USER" value="PLACEHOLDER"/>
		<const name="PluginNamespace\Tests\DB_PASSWORD" value="PLACEHOLDER"/>
		<const name="PluginNamespace\Tests\DB_HOST" value="localhost"/>
		<const name="PluginNamespace\Tests\DB_CHARSET" value="utf8"/>
		<const name="PluginNamespace\Tests\DB_COLLATE" value=""/>
		<const name="PluginNamespace\Tests\DB_TABLE_PREFIX" value="wp_"/>
	</php>
	<testsuites>
		<testsuite name="Integration">
			<directory suffix="Test.php" phpVersion="5.6.0" phpVersionOperator=">=">./tests/Integration</directory>
		</testsuite>
	</testsuites>
	<logging>
		<log type="coverage-clover" target="build/logs/clover.xml"/>
	</logging>
	<filter>
		<whitelist>
			<directory>./src</directory>
			<exclude>
				<directory>./assets</directory>
				<directory>./languages</directory>
				<directory>./resources</directory>
				<directory>./tests</directory>
				<directory>./vendor</directory>
			</exclude>
		</whitelist>
	</filter>
</phpunit>
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         printerClass="Codedungeon\PHPUnitPrettyResultPrinter\Printer">
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <listeners>
        <listener class="NunoMaduro\Collision\Adapters\Phpunit\Listener" />
    </listeners>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="MAIL_DRIVER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>
    </php>
</phpunit>