Full unit test with 100% coverage

This commit is contained in:
Jérémy Munsch 2021-09-12 00:12:49 +02:00
parent a7617753b1
commit fdfb6af639
No known key found for this signature in database
GPG key ID: 677E1F9F80C16560
13 changed files with 625 additions and 129 deletions

View file

@ -4,6 +4,9 @@
<include>
<directory suffix=".php">src/</directory>
</include>
<exclude>
<file>src/EnumServiceProvider.php</file>
</exclude>
<report>
<html outputDirectory="build/coverage"/>
<text outputFile="build/coverage.txt"/>
@ -15,4 +18,9 @@
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
</phpunit>

View file

@ -21,7 +21,9 @@ abstract class BaseEnumRoutable extends BaseEnum implements UrlRoutable
*/
public function getRouteKey()
{
// @codeCoverageIgnoreStart
throw new NotImplementedException('getRouteKey not implemented, should not be used.');
// @codeCoverageIgnoreEnd
}
/**
@ -31,7 +33,9 @@ abstract class BaseEnumRoutable extends BaseEnum implements UrlRoutable
*/
public function getRouteKeyName()
{
// @codeCoverageIgnoreStart
throw new NotImplementedException('getRouteKeyName not implemented, should not be used.');
// @codeCoverageIgnoreEnd
}
/**
@ -72,6 +76,8 @@ abstract class BaseEnumRoutable extends BaseEnum implements UrlRoutable
*/
public function resolveChildRouteBinding($childType, $value, $field)
{
// @codeCoverageIgnoreStart
throw new NotImplementedException('resolveChildRouteBinding not implemented, should not be used.');
// @codeCoverageIgnoreEnd
}
}

View file

@ -0,0 +1,247 @@
<?php
namespace Kwaadpepper\Enum\Tests;
use App\Http\Requests\PassDayRequest;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Kwaadpepper\Enum\Exceptions\UnknownEnumClass;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Kwaadpepper\Enum\Tests\Enums\ContactFormCivility;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Tests\Enums\ForceStringsFromInteger;
use Kwaadpepper\Enum\Tests\Enums\NotRoutableEnum;
use Kwaadpepper\Enum\Tests\Models\Alarm;
use Kwaadpepper\Enum\Tests\Models\Journal;
use Kwaadpepper\Enum\Tests\Models\Report;
use Orchestra\Testbench\TestCase;
use SplFileObject;
class BaseEnumRoutableTest extends TestCase
{
/**
* Defines aliases
*
* @param [type] $app
* @return void
*/
protected function getPackageAliases($app)
{
$app->alias(Alarm::class, 'Alarm');
$app->alias(Journal::class, 'Journal');
$app->alias(Report::class, 'Report');
$app->alias(Days::class, 'Days');
$app->alias(ContactFormCivility::class, 'ContactFormCivility');
$app->alias(ForceStringsFromInteger::class, 'ForceStringsFromInteger');
$app->alias(NotRoutableEnum::class, 'NotRoutableEnum');
}
/**
* Get package providers.
*
* @param \Illuminate\Foundation\Application $app
*
* @return array
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
protected function getPackageProviders($app)
{
return [
'Kwaadpepper\Enum\EnumServiceProvider',
];
}
/**
* Defines migrations
*
* @return void
*/
protected function defineDatabaseMigrations()
{
Schema::create('reports', function (Blueprint $table) {
$table->bigIncrements('id');
$table->smallInteger('day');
});
Schema::create('alarms', function (Blueprint $table) {
$table->bigIncrements('id');
$table->smallInteger('day');
});
Schema::create('journals', function (Blueprint $table) {
$table->bigIncrements('id');
$table->smallInteger('day');
});
}
/**
* Defines seeders
*
* @return void
*/
protected function defineDatabaseSeeders()
{
Report::create(['day' => Days::none()]);
Report::create(['day' => Days::mon()]);
Report::create(['day' => Days::tue()]);
Report::create(['day' => Days::wed()]);
Report::create(['day' => Days::thu()]);
Report::create(['day' => Days::fri()]);
Report::create(['day' => Days::sat()]);
Report::create(['day' => Days::sun()]);
Alarm::create(['day' => Days::none()]);
Alarm::create(['day' => Days::mon()]);
Alarm::create(['day' => Days::tue()]);
Alarm::create(['day' => Days::wed()]);
Alarm::create(['day' => Days::thu()]);
Alarm::create(['day' => Days::fri()]);
Alarm::create(['day' => Days::sat()]);
Alarm::create(['day' => Days::sun()]);
Journal::create(['day' => Days::none()]);
Journal::create(['day' => Days::mon()]);
Journal::create(['day' => Days::tue()]);
Journal::create(['day' => Days::wed()]);
Journal::create(['day' => Days::thu()]);
Journal::create(['day' => Days::fri()]);
Journal::create(['day' => Days::sat()]);
Journal::create(['day' => Days::sun()]);
}
/**
* Define routes setup.
*
* @param \Illuminate\Routing\Router $router
*
* @return void
*/
protected function defineRoutes($router)
{
$router->get('/journals/{journal}', function (Journal $journal) {
return response()->json([$journal->day]);
})->middleware('bindings');
$router->get('/reports/{report}', function (Report $report) {
return response()->json([$report->day]);
})->middleware('bindings');
$router->get('/alarms/{alarm}', function (Alarm $alarm) {
return response()->json([$alarm->day]);
})->middleware('bindings');
$router->get('/days/{day}', function (Days $day) {
return response()->json([$day]);
})->middleware('bindings');
$router->get('/day-validation', function (PassDayRequest $request) {
return response()->json([Days::make((int)$request->day)]);
})->middleware('bindings');
$router->get('/civilities/{civility}', function (ContactFormCivility $civility) {
return response()->json([$civility]);
})->middleware('bindings');
$router->get('/force/{force}', function (ForceStringsFromInteger $force) {
return response()->json([$force]);
})->middleware('bindings');
$router->get('/notroutable/{notroutable}', function (NotRoutableEnum $notroutable) {
return response()->json([$notroutable]);
})->middleware('bindings');
}
public function testRoutesDays()
{
$response = $this->call('GET', sprintf('/days/%s', Days::none()->value));
$response->assertOk();
$response->assertExactJson([Days::none()]);
$response = $this->call('GET', sprintf('/days/%s', Days::mon()->value));
$response->assertOk();
$response->assertExactJson([Days::mon()]);
}
public function testRoutesCivilities()
{
$response = $this->call('GET', sprintf('/civilities/%s', ContactFormCivility::mme()->value));
$response->assertOk();
$response->assertExactJson([ContactFormCivility::mme()]);
}
public function testRoutesWithEnumIntString()
{
$response = $this->call('GET', sprintf('/force/%s', ForceStringsFromInteger::valueB()->value));
$response->assertOk();
$response->assertExactJson([ForceStringsFromInteger::valueB()]);
}
public function testRoutesWithEnumNotFound()
{
$response = $this->call('GET', '/force/6');
$response->assertNotFound();
}
public function testRoutesWithEnumAsPrimaryKey()
{
$response = $this->call('GET', sprintf('/reports/%s', Days::none()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/reports/%s', Days::mon()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/reports/%s', Days::tue()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/reports/%s', Days::fri()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/reports/%s', 9999));
$response->assertNotFound();
}
public function testRoutesWithEnumAsPrimaryKeyWithoutCastEnums()
{
$response = $this->call('GET', sprintf('/alarms/%s', Days::none()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/alarms/%s', Days::mon()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/alarms/%s', Days::tue()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/alarms/%s', Days::fri()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/alarms/%s', 9999));
$response->assertNotFound();
}
public function testRoutesWithEnumAsPrimaryKeyWithoutCastEnumsAndCast()
{
$response = $this->call('GET', sprintf('/journals/%s', Days::none()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/journals/%s', Days::mon()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/journals/%s', Days::tue()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/journals/%s', Days::fri()->value));
$response->assertOk();
$response = $this->call('GET', sprintf('/journals/%s', 9999));
$response->assertNotFound();
}
public function testRules()
{
$rule = new EnumIsValidRule(Days::class);
$this->assertTrue($rule->passes('day', Days::mon()->value));
$this->assertFalse($rule->passes('day', 9999));
$this->assertFalse($rule->passes('day', Days::mon()));
}
public function testRulesRequest()
{
$response = $this->call('GET', sprintf('/day-validation?day=%s', Days::fri()->value));
$response->assertOk();
}
public function testRulesRequestFail()
{
$response = $this->json('GET', sprintf('/day-validation?day=%s', '"!:'));
$response->assertJsonValidationErrors('day');
}
public function testRulesRequestInvalid()
{
$this->expectException(UnknownEnumClass::class);
new EnumIsValidRule(SplFileObject::class);
}
public function testNotRoutableEnum()
{
$response = $this->json('GET', sprintf('/notroutable/%s', '1'));
$response->assertOk();
}
}

175
tests/BaseEnumTest.php Normal file
View file

@ -0,0 +1,175 @@
<?php
namespace Kwaadpepper\Enum\Tests;
use BadMethodCallException;
use Exception;
use Kwaadpepper\Enum\Exceptions\DuplicateLabelsException;
use Kwaadpepper\Enum\Exceptions\DuplicateValuesException;
use Kwaadpepper\Enum\Exceptions\UnknownEnumProperty;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Tests\Enums\DuplicatedLabels;
use Kwaadpepper\Enum\Tests\Enums\DuplicatedValues;
use Kwaadpepper\Enum\Tests\Enums\ForceStringsFromInteger;
use Kwaadpepper\Enum\Tests\Models\Report;
use Orchestra\Testbench\TestCase;
use ReflectionClass;
use stdClass;
use TypeError;
class BaseEnumTest extends TestCase
{
public function testEnum()
{
$mon = Days::mon();
$tue = Days::tue();
Days::toArray();
Days::toValues();
Days::toLabels();
json_encode($tue);
// basic tests
$this->assertEquals('Monday', $mon->label, 'Days::mon()->value should be equals to `Monday`');
$this->assertEquals(2, $mon->value, 'Days::mon()->value should be equals to `2`');
$this->assertClassHasAttribute('value', Days::class, 'Days should have a value property');
$this->assertClassHasAttribute('label', Days::class, 'Days should have a label property');
// undef prop test
$exception = null;
try {
$mon->propthatdontexists;
} catch (Exception $e) {
$exception = $e;
} finally {
$this->assertEquals(
UnknownEnumProperty::class,
\is_object($exception) ? \get_class($exception) : $exception,
'Accessing non existent prop on base enum should throw an UnknownEnumProperty exception'
);
}
$this->assertEquals('Days', $mon->getEnumName(), 'getEnumName should return the enum class name');
$this->assertEquals('mon', $mon->getDefinition(), 'Days::mon() definition should be `mon`');
$this->assertFalse($mon->equals($tue), 'Days::mon() should not be equals to Days::tue()');
$this->assertTrue($mon->equals($mon), 'Days::mon() should be equals to Days::mon()');
$this->assertIsArray(Days::toArray(), 'toArray method should return an array');
$this->assertIsArray(Days::toValues(), 'toValues method should return an array');
$this->assertIsArray(Days::toLabels(), 'toLabels method should return an array');
$this->assertEquals([
0 => 1,
1 => 2,
2 => 4,
3 => 8,
4 => 16,
5 => 32,
6 => 64,
7 => 128
], Days::toValues(), 'Days::toValues method should return an integer array');
$this->assertEquals([
0 => 'None',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday'
], Days::toLabels(), 'Days::toLabels method should return a string array');
$this->assertEquals([
0 => Days::none(),
1 => Days::mon(),
2 => Days::tue(),
3 => Days::wed(),
4 => Days::thu(),
5 => Days::fri(),
6 => Days::sat(),
7 => Days::sun()
], Days::toArray(), 'Days::toLabels method should return an enum array');
$this->assertEquals(
(string)ForceStringsFromInteger::valueA()->value,
(string)ForceStringsFromInteger::valueA(),
'BaseEnum::__toString should return the value as a string'
);
}
public function testCannotAssignAnEnumValue()
{
$this->expectError();
$enum = Days::mon();
$enum->value = 'anything';
}
public function testCannotAssignAnEnumLabel()
{
$this->expectError();
$enum = Days::mon();
$enum->label = 'anything';
}
/**
* This method is here for code coverage
*
* @return void
*/
public function testHackingEnumValueCouldConfuseCache()
{
$enum = Days::mon();
$refClass = new ReflectionClass($enum);
$property = $refClass->getProperty('value');
$property->setAccessible(true);
$property->setValue($enum, 'anything');
$this->assertEquals('anything', $enum->value);
$this->assertNull(
$enum->getDefinition(),
'BaseEnum::getDefinition should be null when hacking var consing cache'
);
}
public function testValidEnumSet()
{
$this->expectNotToPerformAssertions();
$report = new Report();
$report->day = Days::mon();
$report->day = Days::mon()->value;
}
public function testInvalidEnumSet()
{
$this->expectException(BadMethodCallException::class);
$report = new Report();
$report->day = Days::mon()->label;
}
public function testInvalidEnumSetWithFloat()
{
$this->expectException(TypeError::class);
$report = new Report();
$report->day = 0.1;
}
public function testInvalidEnumSetNotAuthorizedType()
{
$this->expectException(TypeError::class);
$report = new Report();
$report->day = new stdClass();
}
public function testInvalidEnumCanSetNull()
{
$this->expectNotToPerformAssertions();
$report = new Report();
$report->day = null;
}
public function testDuplicatedValues()
{
$this->expectException(DuplicateValuesException::class);
DuplicatedValues::one();
}
public function testDuplicatedLabels()
{
$this->expectException(DuplicateLabelsException::class);
DuplicatedLabels::one();
}
}

View file

@ -1,121 +0,0 @@
<?php
namespace Kwaadpepper\Enum\Tests;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Kwaadpepper\Enum\Tests\Enums\ContactFormCivility;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Tests\Enums\ForceStringsFromInteger;
use Orchestra\Testbench\TestCase;
class EnumTest extends TestCase
{
protected function getPackageAliases($app)
{
$app->alias(Days::class, 'Days');
$app->alias(ContactFormCivility::class, 'ContactFormCivility');
$app->alias(ForceStringsFromInteger::class, 'ForceStringsFromInteger');
}
/**
* Define routes setup.
*
* @param \Illuminate\Routing\Router $router
*
* @return void
*/
protected function defineRoutes($router)
{
$router->get('/days/{day}', function (Days $day) {
return response()->json([$day]);
})->middleware('bindings');
$router->get('/civilities/{civility}', function (ContactFormCivility $civility) {
return response()->json([$civility]);
})->middleware('bindings');
$router->get('/force/{force}', function (ForceStringsFromInteger $force) {
return response()->json([$force]);
})->middleware('bindings');
}
public function testEnum()
{
$mon = Days::mon();
$tue = Days::tue();
Days::toArray();
Days::toValues();
Days::toLabels();
json_encode($tue);
$mon->getDefinition();
$this->assertClassHasAttribute('value', Days::class);
$this->assertClassHasAttribute('label', Days::class);
$this->assertFalse($mon->equals($tue));
$this->assertTrue($mon->equals($mon));
$this->assertIsArray(Days::toArray());
$this->assertIsArray(Days::toValues());
$this->assertIsArray(Days::toLabels());
$this->assertEquals([
0 => 0,
1 => 1,
2 => 2,
3 => 4,
4 => 8,
5 => 16,
6 => 32,
7 => 64
], Days::toValues());
$this->assertEquals([
0 => 'None',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday'
], Days::toLabels());
$this->assertEquals([
0 => Days::none(),
1 => Days::mon(),
2 => Days::tue(),
3 => Days::wed(),
4 => Days::thu(),
5 => Days::fri(),
6 => Days::sat(),
7 => Days::sun()
], Days::toArray());
$this->assertEquals('Monday', (string)$mon);
}
public function testRoutesDays()
{
$response = $this->call('GET', '/days/1');
$response->assertOk();
$response->assertExactJson([Days::mon()]);
$response = $this->call('GET', '/days/2');
$response->assertOk();
$response->assertExactJson([Days::tue()]);
}
public function testRoutesCivilities()
{
$response = $this->call('GET', '/civilities/mme');
$response->assertOk();
$response->assertExactJson([ContactFormCivility::mme()]);
}
public function testRoutesWithEnumIntString()
{
$response = $this->call('GET', '/force/1');
$response->assertOk();
$response->assertExactJson([ForceStringsFromInteger::valueB()]);
}
public function testRules()
{
$rule = new EnumIsValidRule(Days::class);
$this->assertTrue($rule->passes('day', Days::mon()->value));
$this->assertFalse($rule->passes('day', 9999));
$this->assertFalse($rule->passes('day', Days::mon()));
}
}

View file

@ -19,14 +19,14 @@ final class Days extends BaseEnumRoutable
protected static function values(): array
{
return [
'none' => 0,
'mon' => 1,
'tue' => 1 << 1,
'wed' => 1 << 2,
'thu' => 1 << 3,
'fri' => 1 << 4,
'sat' => 1 << 5,
'sun' => 1 << 6,
'none' => 1,
'mon' => 1 << 1,
'tue' => 1 << 2,
'wed' => 1 << 3,
'thu' => 1 << 4,
'fri' => 1 << 5,
'sat' => 1 << 6,
'sun' => 1 << 7,
];
}

View file

@ -0,0 +1,22 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self one()
* @method static self two()
* @method static self three()
*/
final class DuplicatedLabels extends BaseEnumRoutable
{
protected static function labels(): array
{
return [
'one' => 'one',
'two' => 'two',
'three' => 'two'
];
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self one()
* @method static self two()
* @method static self three()
*/
final class DuplicatedValues extends BaseEnumRoutable
{
protected static function values(): array
{
return [
'one' => '0',
'two' => '1',
'three' => '1'
];
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self one()
* @method static self two()
* @method static self three()
*/
final class NotRoutableEnum extends BaseEnum
{
protected static function values(): array
{
return [
'one' => '0',
'two' => '1',
'three' => '2'
];
}
}

23
tests/Models/Alarm.php Normal file
View file

@ -0,0 +1,23 @@
<?php
namespace Kwaadpepper\Enum\Tests\Models;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Alarm extends Model
{
use CastsEnums;
public $timestamps = false;
protected $primaryKey = 'day';
protected $keyType = 'int';
public $incrementing = false;
protected $fillable = [
'day'
];
}

32
tests/Models/Journal.php Normal file
View file

@ -0,0 +1,32 @@
<?php
namespace Kwaadpepper\Enum\Tests\Models;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Journal extends Model
{
use CastsEnums;
public $timestamps = false;
protected $primaryKey = 'day';
protected $keyType = 'int';
public $incrementing = false;
protected $fillable = [
'day'
];
protected $casts = [
'day' => 'int'
];
protected $enumCasts = [
'day' => Days::class
];
}

28
tests/Models/Report.php Normal file
View file

@ -0,0 +1,28 @@
<?php
namespace Kwaadpepper\Enum\Tests\Models;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Report extends Model
{
use CastsEnums;
public $timestamps = false;
protected $primaryKey = 'day';
protected $keyType = 'int';
public $incrementing = false;
protected $fillable = [
'day'
];
protected $enumCasts = [
'day' => Days::class
];
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Kwaadpepper\Enum\Tests\Enums\Days;
class PassDayRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'day' => ['required', new EnumIsValidRule(Days::class)],
];
}
}