Skip to content

Commit

Permalink
Add first version
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathias STRASSER committed Oct 11, 2019
1 parent cf27e2d commit 3467f19
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
###> project ###
/composer.lock
/vendor/
###< project ###
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# DTO Tester

Automatically PHPUnit Test DTO and Transfer Objects.

Original idea: [Automatically JUnit Test DTO and Transfer Objects](https://objectpartners.com/2016/02/16/automatically-junit-test-dto-and-transfer-objects/)

## Installation

These commands requires you to have [Composer](https://getcomposer.org/download/) installed globally.
Open a command console, enter your project directory and execute the following
commands to download the latest stable version:

```sh
composer require --dev roukmoute/dto-tester
```

## Usage

All we need to do is extend `DtoTester\DtoTest` and create a test instance and
the `DtoTest` class will do the rest.

Here it is an example class named `FooBar`:

```php
<?php

class FooBarTest extends \DtoTester\DtoTest
{
protected function getInstance()
{
return new FooBar();
}
}
```

So we now turned what would have been many boring unit tests which didn’t test
any real business logic into a simple file with less than 10 lines of code.
40 changes: 40 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "roukmoute/dto-tester",
"description": "Add PHPUnit extension for testing DTOs and Transfer Objects",
"type": "library",
"keywords": ["test", "tests", "testing", "DTO"],
"require": {
"php": ">=7.1",
"phpunit/phpunit": "^8.4",
"roave/better-reflection": "^3.5"
},
"license": "MIT",
"authors": [
{
"name": "Mathias STRASSER",
"email": "[email protected]"
}
],
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"DtoTester\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"PHPUnit\\": "tests/PHPUnit"
}
},
"minimum-stability": "stable",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
}
}
30 changes: 30 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>

<testsuites>
<testsuite name="DTO Tester Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
114 changes: 114 additions & 0 deletions src/DtoTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

/*
* This file is part of the roukmoute/dto-tester package.
*
* (c) Mathias STRASSER <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace DtoTester;

use PHPUnit\Framework\TestCase;
use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflection\ReflectionMethod;
use RuntimeException;

abstract class DtoTest extends TestCase
{
/** @var MapperCollection */
private $mappers;

public function __construct($name = null, array $data = [], $dataName = '')
{
$this->mappers = MapperCollection::defaultMappers();

parent::__construct($name, $data, $dataName);
}

public function testGettersAndSetters()
{
$getterSetterMapping = [];

$instance = $this->getInstance();

$this->foundMappings($instance, $getterSetterMapping);
$this->assetMappings($getterSetterMapping, $instance);
}

abstract protected function getInstance();

private function foundMappings($instance, array &$getterSetterMapping): void
{
foreach (ReflectionClass::createFromInstance($instance)->getMethods() as $method) {
$methodName = $method->getName();
$numberOfParameters = $method->getNumberOfParameters();

if ((mb_substr($methodName, 0, 3) === 'get' && $numberOfParameters === 0)
|| (mb_substr($methodName, 0, 3) === 'set' && $numberOfParameters === 1)
|| (mb_substr($methodName, 0, 2) === 'is' && $numberOfParameters === 0)
) {
$objectName = mb_substr($methodName, mb_substr($methodName, 0, 2) === 'is' ? 2 : 3);

$getterSettingPair = $this->getterSettingPair($getterSetterMapping, $objectName);

if (mb_substr($methodName, 0, 3) === 'set') {
$getterSettingPair->setSetter($method);
} else {
$getterSettingPair->setGetter($method);
}
}
}
}

private function getterSettingPair(array &$getterSetterMapping, string $objectName): GetterSetterPair
{
if (isset($getterSetterMapping[$objectName])) {
return $getterSetterMapping[$objectName];
}

$getterSettingPair = new GetterSetterPair();
$getterSetterMapping[$objectName] = $getterSettingPair;

return $getterSettingPair;
}

private function assetMappings(array &$getterSetterMapping, $instance): void
{
/** @var GetterSetterPair $pair */
foreach ($getterSetterMapping as $objectName => $pair) {
$fieldName = mb_strtolower($objectName[0]) . mb_substr($objectName, 1);

if ($pair->hasGetterAndSetter()) {
$parameterType = $pair->setter()->getParameters()[0]->getType();
$newObject = $this->createObject($fieldName, (string) $parameterType);

$pair->setter()->invoke($instance, $newObject);

$this->callGetter($fieldName, $pair->getter(), $instance, $newObject);
}
}
}

private function createObject(string $fieldName, string $class)
{
try {
return $this->mappers->get($class)();
} catch (\Exception $exception) {
throw new RuntimeException(sprintf('Unable to create objects for field "%s".', $fieldName));
}
}

private function callGetter(string $fieldName, ReflectionMethod $getter, $instance, $expected)
{
if ($getter->getReturnType()->isBuiltin()) {
$this->assertEquals($expected, $getter->invoke($instance), sprintf('"$%s" is different', $fieldName));
} else {
$this->assertSame($expected, $getter->invoke($instance), sprintf('"$%s" is different', $fieldName));
}
}
}
50 changes: 50 additions & 0 deletions src/GetterSetterPair.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of the roukmoute/dto-tester package.
*
* (c) Mathias STRASSER <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace DtoTester;

use Roave\BetterReflection\Reflection\ReflectionMethod;

class GetterSetterPair
{
/** @var ReflectionMethod */
public $getter;

/** @var ReflectionMethod */
public $setter;

public function getter(): ReflectionMethod
{
return $this->getter;
}

public function setter(): ReflectionMethod
{
return $this->setter;
}

public function hasGetterAndSetter(): bool
{
return $this->getter && $this->setter;
}

public function setSetter(ReflectionMethod $method): void
{
$this->setter = $method;
}

public function setGetter(ReflectionMethod $method): void
{
$this->getter = $method;
}
}
54 changes: 54 additions & 0 deletions src/MapperCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* This file is part of the roukmoute/dto-tester package.
*
* (c) Mathias STRASSER <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace DtoTester;

class MapperCollection
{
private $collection;

public function __construct()
{
$this->collection = [];
}

public static function defaultMappers()
{
$self = new self();

$self->put('array', function () {return []; });
$self->put('bool', function () {return true; });
$self->put('float', function () {return 0.0; });
$self->put('int', function () {return 0; });
$self->put('string', function () {return ''; });

$self->put(\DateInterval::class, function () {return new \DateInterval(); });
$self->put(\DatePeriod::class, function () {return new \DatePeriod(); });
$self->put(\DateTime::class, function () {return new \DateTime(); });
$self->put(\DateTimeImmutable::class, function () {return new \DateTimeImmutable(); });
$self->put(\DateTimeInterface::class, function () {return new \DateTimeImmutable(); });
$self->put(\DateTimeZone::class, function () {return new \DateTimeZone(); });

return $self;
}

public function put(string $class, callable $supplier)
{
$this->collection[$class] = $supplier;
}

public function get(string $class)
{
return $this->collection[$class];
}
}
Loading

0 comments on commit 3467f19

Please sign in to comment.