Skip to content

Commit 3467f19

Browse files
author
Mathias STRASSER
committed
Add first version
1 parent cf27e2d commit 3467f19

File tree

9 files changed

+435
-0
lines changed

9 files changed

+435
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
###> project ###
2+
/composer.lock
3+
/vendor/
4+
###< project ###

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# DTO Tester
2+
3+
Automatically PHPUnit Test DTO and Transfer Objects.
4+
5+
Original idea: [Automatically JUnit Test DTO and Transfer Objects](https://objectpartners.com/2016/02/16/automatically-junit-test-dto-and-transfer-objects/)
6+
7+
## Installation
8+
9+
These commands requires you to have [Composer](https://getcomposer.org/download/) installed globally.
10+
Open a command console, enter your project directory and execute the following
11+
commands to download the latest stable version:
12+
13+
```sh
14+
composer require --dev roukmoute/dto-tester
15+
```
16+
17+
## Usage
18+
19+
All we need to do is extend `DtoTester\DtoTest` and create a test instance and
20+
the `DtoTest` class will do the rest.
21+
22+
Here it is an example class named `FooBar`:
23+
24+
```php
25+
<?php
26+
27+
class FooBarTest extends \DtoTester\DtoTest
28+
{
29+
protected function getInstance()
30+
{
31+
return new FooBar();
32+
}
33+
}
34+
```
35+
36+
So we now turned what would have been many boring unit tests which didn’t test
37+
any real business logic into a simple file with less than 10 lines of code.

composer.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "roukmoute/dto-tester",
3+
"description": "Add PHPUnit extension for testing DTOs and Transfer Objects",
4+
"type": "library",
5+
"keywords": ["test", "tests", "testing", "DTO"],
6+
"require": {
7+
"php": ">=7.1",
8+
"phpunit/phpunit": "^8.4",
9+
"roave/better-reflection": "^3.5"
10+
},
11+
"license": "MIT",
12+
"authors": [
13+
{
14+
"name": "Mathias STRASSER",
15+
"email": "contact@roukmoute.fr"
16+
}
17+
],
18+
"config": {
19+
"preferred-install": {
20+
"*": "dist"
21+
},
22+
"sort-packages": true
23+
},
24+
"autoload": {
25+
"psr-4": {
26+
"DtoTester\\": "src/"
27+
}
28+
},
29+
"autoload-dev": {
30+
"psr-4": {
31+
"PHPUnit\\": "tests/PHPUnit"
32+
}
33+
},
34+
"minimum-stability": "stable",
35+
"extra": {
36+
"branch-alias": {
37+
"dev-master": "1.0-dev"
38+
}
39+
}
40+
}

phpunit.xml.dist

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
5+
backupGlobals="false"
6+
colors="true"
7+
bootstrap="vendor/autoload.php"
8+
failOnRisky="true"
9+
failOnWarning="true"
10+
>
11+
<php>
12+
<ini name="error_reporting" value="-1" />
13+
</php>
14+
15+
<testsuites>
16+
<testsuite name="DTO Tester Test Suite">
17+
<directory>./tests/</directory>
18+
</testsuite>
19+
</testsuites>
20+
21+
<filter>
22+
<whitelist>
23+
<directory>./</directory>
24+
<exclude>
25+
<directory>./tests</directory>
26+
<directory>./vendor</directory>
27+
</exclude>
28+
</whitelist>
29+
</filter>
30+
</phpunit>

src/DtoTest.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the roukmoute/dto-tester package.
5+
*
6+
* (c) Mathias STRASSER <contact@roukmoute.fr>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace DtoTester;
15+
16+
use PHPUnit\Framework\TestCase;
17+
use Roave\BetterReflection\Reflection\ReflectionClass;
18+
use Roave\BetterReflection\Reflection\ReflectionMethod;
19+
use RuntimeException;
20+
21+
abstract class DtoTest extends TestCase
22+
{
23+
/** @var MapperCollection */
24+
private $mappers;
25+
26+
public function __construct($name = null, array $data = [], $dataName = '')
27+
{
28+
$this->mappers = MapperCollection::defaultMappers();
29+
30+
parent::__construct($name, $data, $dataName);
31+
}
32+
33+
public function testGettersAndSetters()
34+
{
35+
$getterSetterMapping = [];
36+
37+
$instance = $this->getInstance();
38+
39+
$this->foundMappings($instance, $getterSetterMapping);
40+
$this->assetMappings($getterSetterMapping, $instance);
41+
}
42+
43+
abstract protected function getInstance();
44+
45+
private function foundMappings($instance, array &$getterSetterMapping): void
46+
{
47+
foreach (ReflectionClass::createFromInstance($instance)->getMethods() as $method) {
48+
$methodName = $method->getName();
49+
$numberOfParameters = $method->getNumberOfParameters();
50+
51+
if ((mb_substr($methodName, 0, 3) === 'get' && $numberOfParameters === 0)
52+
|| (mb_substr($methodName, 0, 3) === 'set' && $numberOfParameters === 1)
53+
|| (mb_substr($methodName, 0, 2) === 'is' && $numberOfParameters === 0)
54+
) {
55+
$objectName = mb_substr($methodName, mb_substr($methodName, 0, 2) === 'is' ? 2 : 3);
56+
57+
$getterSettingPair = $this->getterSettingPair($getterSetterMapping, $objectName);
58+
59+
if (mb_substr($methodName, 0, 3) === 'set') {
60+
$getterSettingPair->setSetter($method);
61+
} else {
62+
$getterSettingPair->setGetter($method);
63+
}
64+
}
65+
}
66+
}
67+
68+
private function getterSettingPair(array &$getterSetterMapping, string $objectName): GetterSetterPair
69+
{
70+
if (isset($getterSetterMapping[$objectName])) {
71+
return $getterSetterMapping[$objectName];
72+
}
73+
74+
$getterSettingPair = new GetterSetterPair();
75+
$getterSetterMapping[$objectName] = $getterSettingPair;
76+
77+
return $getterSettingPair;
78+
}
79+
80+
private function assetMappings(array &$getterSetterMapping, $instance): void
81+
{
82+
/** @var GetterSetterPair $pair */
83+
foreach ($getterSetterMapping as $objectName => $pair) {
84+
$fieldName = mb_strtolower($objectName[0]) . mb_substr($objectName, 1);
85+
86+
if ($pair->hasGetterAndSetter()) {
87+
$parameterType = $pair->setter()->getParameters()[0]->getType();
88+
$newObject = $this->createObject($fieldName, (string) $parameterType);
89+
90+
$pair->setter()->invoke($instance, $newObject);
91+
92+
$this->callGetter($fieldName, $pair->getter(), $instance, $newObject);
93+
}
94+
}
95+
}
96+
97+
private function createObject(string $fieldName, string $class)
98+
{
99+
try {
100+
return $this->mappers->get($class)();
101+
} catch (\Exception $exception) {
102+
throw new RuntimeException(sprintf('Unable to create objects for field "%s".', $fieldName));
103+
}
104+
}
105+
106+
private function callGetter(string $fieldName, ReflectionMethod $getter, $instance, $expected)
107+
{
108+
if ($getter->getReturnType()->isBuiltin()) {
109+
$this->assertEquals($expected, $getter->invoke($instance), sprintf('"$%s" is different', $fieldName));
110+
} else {
111+
$this->assertSame($expected, $getter->invoke($instance), sprintf('"$%s" is different', $fieldName));
112+
}
113+
}
114+
}

src/GetterSetterPair.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the roukmoute/dto-tester package.
5+
*
6+
* (c) Mathias STRASSER <contact@roukmoute.fr>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace DtoTester;
15+
16+
use Roave\BetterReflection\Reflection\ReflectionMethod;
17+
18+
class GetterSetterPair
19+
{
20+
/** @var ReflectionMethod */
21+
public $getter;
22+
23+
/** @var ReflectionMethod */
24+
public $setter;
25+
26+
public function getter(): ReflectionMethod
27+
{
28+
return $this->getter;
29+
}
30+
31+
public function setter(): ReflectionMethod
32+
{
33+
return $this->setter;
34+
}
35+
36+
public function hasGetterAndSetter(): bool
37+
{
38+
return $this->getter && $this->setter;
39+
}
40+
41+
public function setSetter(ReflectionMethod $method): void
42+
{
43+
$this->setter = $method;
44+
}
45+
46+
public function setGetter(ReflectionMethod $method): void
47+
{
48+
$this->getter = $method;
49+
}
50+
}

src/MapperCollection.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the roukmoute/dto-tester package.
5+
*
6+
* (c) Mathias STRASSER <contact@roukmoute.fr>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace DtoTester;
15+
16+
class MapperCollection
17+
{
18+
private $collection;
19+
20+
public function __construct()
21+
{
22+
$this->collection = [];
23+
}
24+
25+
public static function defaultMappers()
26+
{
27+
$self = new self();
28+
29+
$self->put('array', function () {return []; });
30+
$self->put('bool', function () {return true; });
31+
$self->put('float', function () {return 0.0; });
32+
$self->put('int', function () {return 0; });
33+
$self->put('string', function () {return ''; });
34+
35+
$self->put(\DateInterval::class, function () {return new \DateInterval(); });
36+
$self->put(\DatePeriod::class, function () {return new \DatePeriod(); });
37+
$self->put(\DateTime::class, function () {return new \DateTime(); });
38+
$self->put(\DateTimeImmutable::class, function () {return new \DateTimeImmutable(); });
39+
$self->put(\DateTimeInterface::class, function () {return new \DateTimeImmutable(); });
40+
$self->put(\DateTimeZone::class, function () {return new \DateTimeZone(); });
41+
42+
return $self;
43+
}
44+
45+
public function put(string $class, callable $supplier)
46+
{
47+
$this->collection[$class] = $supplier;
48+
}
49+
50+
public function get(string $class)
51+
{
52+
return $this->collection[$class];
53+
}
54+
}

0 commit comments

Comments
 (0)