Docblock Unit Testing
php-tt is a lightweight PHP library that allows you to write unit tests for methods of your class right in the docblock of the method, saving you lots of time and effort
Let's get started
Install the package
composer require --dev radziuk/php-tt
Run tests
php vendor/radziuk/php-tt/bin/run.php
Use Laravel integrated runner
php vendor/radziuk/php-tt/bin/lararun.php
More info
Specify the folder
By default the runner uses the "app" folder to look for tests. You can specify a custom folder
php vendor/radziuk/php-tt/bin/run.php src/lib
Self-test the package
After the package is install you can run the selftest script included in the package
php vendor/radziuk/php-tt/bin/selftest.php
Should input something like this:
Info: Assertions done: 89
Info: Assertions true: 89
Test examples
Simple test, pass "hello" to the function, should return "hello"
/**
* @php-tt-assert "hello" >>> "hello"
*/
public function replaceMarkers(string $string): string
Text example #2
/**
* @php-tt-assert "'hello', 'world'" >>> "#1, #2"
*/
public function replaceMarkers(string $string): string
Text example #3
/**
* @php-tt-assert "'hello', 'world'", 1 >>> "#1, 'world'"
*/
public function replaceMarkers(string $string, int $count): string
Text example #4, passing an array
/**
* @php-tt-assert "'hello', 'world'", ['hello' => 'world'] >>> "#1, 'world'"
*/
public function replaceMarkers(string $string, array $data): string
Text example #5, calling a method on the object before testing
/**
* @php-tt-before $object->setPattern('#%s')
* @php-tt-assert "'hello', 'world'", ['hello' => 'world'] >>> "#1, 'world'"
*/
public function replaceMarkers(string $string, array $data): string
Text example #6, mocking $this->... method
/**
* @php-tt-mock $this->getFilenameFromDatasource >>> 'test.php'
* @php-tt-assert 'Any.key', 'default' >>> ['default/test.php']
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
{
$dataDir = $this->getDataDirForMethod($methodName);
$fileName = $this->getFilenameFromDatasource($dataSource);
$file = $dataDir . '/' . $fileName;
return [$file];
}
Text example #7, mocking method of a dependency
/**
* @php-tt-mock $this->service->getFilenameFromDatasource >>> 'test.php'
* @php-tt-assert 'Any.key', 'default' >>> ['default/test.php']
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
{
$dataDir = $this->getDataDirForMethod($methodName);
$fileName = $this->service->getFilenameFromDatasource($dataSource);
$file = $dataDir . '/' . $fileName;
return [$file];
}
Text example #8, mocking global functions and methods
/**
* @php-tt-mock $service->getFilenameFromDatasource >>> 'test.php'
* @php-tt-mock getDataDirForMethod >>> 'default'
* @php-tt-assert 'Any.key', 'default' >>> ['default/test.php']
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
{
$dataDir = getDataDirForMethod($methodName);
$fileName = $service->getFilenameFromDatasource($dataSource);
$file = $dataDir . '/' . $fileName;
return [$file];
}
Text example #9, mocking a property
/**
* @php-tt-mock @$service->getFilenameFromDatasource >>> 'test.php'
* @php-tt-mock $this->data_dir >>> '/var/www'
* @php-tt-assert 'Any.key', 'default' >>> ['/var/www/test.php']
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
{
$dataDir = $this->data_dir;
$fileName = $service->getFilenameFromDatasource($dataSource);
$file = $dataDir . '/' . $fileName;
return [$file];
}
Text example #10, mocking anything
/**
* @php-tt-mock self::getFilenameFromDatasource >>> 'test.php'
* @php-tt-exact-mock require $methodName >>> '/var/www'
* @php-tt-assert 'Any.key', 'default' >>> ['/var/www/test.php']
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
{
$dataDir = require $methodName;
$fileName = self::getFilenameFromDatasource($dataSource);
$file = $dataDir . '/' . $fileName;
return [$file];
}
Define your test data in a separate file
By default, the runner is looking for tests/php-tt-data folder, so you can create your data files there
Create a data file
touch tests/php-tt-data/TestData.php
In your data file
<?php
return [
'getFileForDataSource' => [
0 => [
['DoTest.test', 'methodName'],//parameters as array
'test',//expected result
],
1 => [
['DoTest', 'methodName'],
'methodName'
]
],
];
In your class
/**
* @php-tt-mock @self::getFilenameFromDatasource >>> 'test.php'
* @php-tt-exact-mock require $methodName >>> '/var/www'
* @php-tt-data TestData
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
Use custom keys in your data file. The default key is the method name.
In your data file
<?php
return [
'__my_custom_key__' => [
0 => [
['DoTest.test', 'methodName'],//parameters as array
'test',//expected result
],
1 => [
['DoTest', 'methodName'],
'methodName'
]
],
];
In your class
/**
* @php-tt-mock self::getFilenameFromDatasource >>> 'test.php'
* @php-tt-exact-mock require $methodName >>> '/var/www'
* @php-tt-data TestData.__my_custom_key__
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
Use data files to create mocks
In your class
/**
* @php-tt-mock self::getFilenameFromDatasource >>> 'test.php'
* @php-tt-exact-mock require $methodName >>> #TestData.my_mock
* @php-tt-data TestData
*/
private function getFileForDataSource(string $dataSource, string $methodName): array
In your data file
<?php
return [
'my_mock' => (function(){
return 'hello';
})(),
];
Other assertions
Assert exception.
This asserts that the method will throw any exception
/**
* @php-tt-assert-exception "'hello', 'world'"
*/
public function replaceMarkers(string $string): string
This asserts that the method will throw an exception of a certain class
/**
* @php-tt-assert-exception "'hello', 'world'" >>> \App\My\Exception::class
*/
public function replaceMarkers(string $string): string
Assert exception contains
This asserts that the method will throw an exception and the message will contain the text
/**
* @php-tt-assert-exception-contains "'hello', 'world'" >>> "My error text"
*/
public function replaceMarkers(string $string): string
Assert contains & preg
This asserts that the result will contain the specified substring
/**
* @php-tt-assert-contains "'hello', 'world'" >>> "My error text"
*/
public function replaceMarkers(string $string): string
This asserts that the result will match the specified regular expression
/**
* @php-tt-assert-preg "'hello', 'world'" >>> "/^.*$/"
*/
public function replaceMarkers(string $string): string
Assert callable
This assertion allows you do define a callable that will handle the result of the method's execution and perform comparison with the expected result
/**
* @php-tt-assert-callable "hello, world" >>> [#TestData.my_callable, 'expected result']
*/
public function replaceMarkers(string $string): string
In your tests/php-tt-data/TestData.php
return [
'my_callable' => function(stdClass $result, $expected):bool {
return $result->property === $expected;
},
];
Pass more parameters
/**
* @php-tt-assert-callable "hello, world" >>> [#TestData.my_callable, 'expected result', false]
*/
public function replaceMarkers(string $string): string
In your tests/php-tt-data/TestData.php
return [
'my_callable' => function(stdClass $result, $expected, $true = true):bool {
return $true ? $result->property === $expected : $result->property !== $expected;
},
];
Create an alias
/**
* @php-tt-alias "property-equals" >>> #TestData.property_equals
* @php-tt-alias "property-not-equals" >>> #TestData.property_not_equals
* use your aliases
* @php-tt-assert-property-equals 'parameter' >>> 'expected'
* @php-tt-assert-property-not-equals 'parameter' >>> 'expected'
*/
public function replaceMarkers(string $string): string
In your tests/php-tt-data/TestData.php
return [
'property_equals' => function(stdClass $result, $expected):bool {
return $result->property === $expected;
},
'property_not_equals' => function(stdClass $result, $expected):bool {
return $result->property !== $expected;
},
];
Use custom data folder
php vendor/radziuk/php-tt/bin/run.php app tests/my-folder
Increase verbosity of the output
php vendor/radziuk/php-tt/bin/run.php 3
Any 1-digit number is interpreted as verbosity level. You can specify you custom folder and verbosity level like the following:
php vendor/radziuk/php-tt/bin/run.php custom/app 3
php vendor/radziuk/php-tt/bin/run.php custom/app custom/data 3
Create your custom runner
touch testrunner.php
<?php
require __DIR__.'/vendor/autoload.php';
$tt = new \Aradziuk\PhpTT\Tt();
$tt->run(
__DIR__ . '/app', //dir with your classes
__DIR__ . '/test/php-tt' // dir with your data
);
Support of traits
As of version 0.3 the support of trait has been added. During the testing, an anonymous class implementing the trait is initialized. To override this default behaviour you can use @php-tt-use-class
trait MyTrait {
/**
* @php-tt-use-class My\Namespace\CustomClass
* the above command will tell php-tt to use the object of My\Namespace\CustomClass for testing this method. The class should use the trait
* @php-tt-assert "#1, #2" >>> 'hello, world'
*/
public function replaceMarkers(string $string): string
Laravel integration
lararun.php
lararun.php boots your laravel without database, logs and other providers, so in your tests you have access to lots of Laravel functionality, eg. facades, various providers etc Please note that currently lararun.php is in alpha version, and it is highly recommended to mock out all the code that can cause permanent changes to your data
php vendor/radziuk/php-tt/bin/lararun.php
Create your custom Laravel command
php artisan make:command PhpTT
In app/Console/Commands/PhpTT.php
$tt = new \Radziuk\PhpTT\Tt();
$tt->setOutputCallback('info', function (string $string) {
$this->info($string);
})->setOutputCallback('error', function (string $string){
$this->error($string);
})->setOutputCallback('alert', function (string $string){
$this->alert($string);
}); // use artisan generic out put
$tt->run(
app_path(),
base_path('tests/php-tt-data')
);
In app/Console/Kernel.php
protected $commands = [
PhpTT::class,
];
php artisan app:php-tt
Create your custom assertion (same as alias)
\Radziuk\PhpTT\Tt::enhance('greater-than', function(\ReflectionMethod $method, $object, array $params, $expected): array
{
$result = $method->invoke($object, ...$params);
return [$result > $expected, $result];
});
In your tests
/**
* @php-tt-assert-greater-than 2, 2 >>> 3
*/
public function multiply(int $x, int $y): int