Skip to content

Commit

Permalink
feat(testing): Add ability to test RunInTransaction/SafeUniqueSave ac…
Browse files Browse the repository at this point in the history
…tions using assert/expectation classes

- Using contract allows switching implementation
- Assert triggers closures in expected "way"
  • Loading branch information
pionl committed Sep 29, 2022
1 parent 964a854 commit a776d5f
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/Database/Actions/RunInTransactionAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

use Closure;
use Illuminate\Database\Connection;
use LaraStrict\Database\Contracts\RunInTransactionActionContract;

class RunInTransactionAction
class RunInTransactionAction implements RunInTransactionActionContract
{
public function __construct(private readonly Connection $connection)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Database/Actions/SafeUniqueSaveAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use LaraStrict\Database\Contracts\SafeUniqueSaveActionContract;

class SafeUniqueSaveAction
class SafeUniqueSaveAction implements SafeUniqueSaveActionContract
{
protected const DUPLICATION_ERROR_CODE = 1062;

Expand Down
19 changes: 19 additions & 0 deletions src/Database/Contracts/RunInTransactionActionContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Database\Contracts;

use Closure;

interface RunInTransactionActionContract
{
/**
* @template T
*
* @param Closure():T $callback
*
* @return T
*/
public function execute(Closure $callback, int $attempts = 1): mixed;
}
24 changes: 24 additions & 0 deletions src/Database/Contracts/SafeUniqueSaveActionContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Database\Contracts;

use Closure;
use Illuminate\Database\Eloquent\Model;

interface SafeUniqueSaveActionContract
{
/**
* Ensures that model will be reset if duplication error has occurred.
*
* @template T of Model
* @template R
*
* @param T $model
* @param Closure(T, int): R $setupClosure
*
* @return R Returns value from the closure (the latest value that was successfully stored)
*/
public function execute(Model $model, Closure $setupClosure, int $maxTries = 20, int $tries = 1): mixed;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Testing\Database\Contracts;

use Closure;
use LaraStrict\Database\Contracts\RunInTransactionActionContract;
use LaraStrict\Testing\AbstractExpectationCallMap;
use PHPUnit\Framework\Assert;

/**
* @extends AbstractExpectationCallMap<RunInTransactionActionContractExpectation>
*/
class RunInTransactionActionContractAssert extends AbstractExpectationCallMap implements RunInTransactionActionContract
{
public function __construct(array $expectationMap = [
new RunInTransactionActionContractExpectation(false),
], int $callStep = 0)
{
parent::__construct($expectationMap, $callStep);
}

/**
* @template T
*
* @param Closure():T $callback
*
* @return T
*/
public function execute(Closure $callback, int $attempts = 1): mixed
{
$expectation = $this->getExpectation();
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->attempts, $attempts, $message);

$result = $callback();

if ($expectation->fail) {
return $this->execute($callback, $attempts + 1);
}

return $result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Testing\Database\Contracts;

final class RunInTransactionActionContractExpectation
{
public function __construct(
public readonly bool $fail,
public readonly int $attempts = 1,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Testing\Database\Contracts;

use Closure;
use Illuminate\Database\Eloquent\Model;
use LaraStrict\Database\Contracts\SafeUniqueSaveActionContract;
use LaraStrict\Testing\AbstractExpectationCallMap;
use PHPUnit\Framework\Assert;

/**
* @extends AbstractExpectationCallMap<SafeUniqueSaveActionContractExpectation>
*/
class SafeUniqueSaveActionContractAssert extends AbstractExpectationCallMap implements SafeUniqueSaveActionContract
{
/**
* Ensures that model will be reset if duplication error has occurred.
*
* @template T of Model
* @template R
*
* @param T $model
* @param Closure(T, int): R $setupClosure
*
* @return R Returns value from the closure (the latest value that was successfully stored)
*/
public function execute(Model $model, Closure $setupClosure, int $maxTries = 20, int $tries = 1): mixed
{
$expectation = $this->getExpectation();
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->model, $model, $message);
Assert::assertEquals($expectation->maxTries, $maxTries, $message);
Assert::assertEquals($expectation->tries, $tries, $message);

$result = $setupClosure($model, $tries);

if ($expectation->fail) {
return $this->execute($model, $setupClosure, $maxTries, $tries + 1);
}

return $result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Testing\Database\Contracts;

use Illuminate\Database\Eloquent\Model;

final class SafeUniqueSaveActionContractExpectation
{
public function __construct(
public readonly Model $model,
public readonly bool $fail = false,
public readonly int $maxTries = 20,
public readonly int $tries = 1,
) {
}
}
6 changes: 6 additions & 0 deletions tests/Feature/Providers/LaraStrictServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

namespace Tests\LaraStrict\Feature\Providers;

use LaraStrict\Database\Actions\RunInTransactionAction;
use LaraStrict\Database\Actions\SafeUniqueSaveAction;
use LaraStrict\Database\Contracts\RunInTransactionActionContract;
use LaraStrict\Database\Contracts\SafeUniqueSaveActionContract;
use LaraStrict\Testing\Actions\GetBasePathForStubsAction;
use LaraStrict\Testing\Actions\GetNamespaceForStubsAction;
use LaraStrict\Testing\Contracts\GetBasePathForStubsActionContract;
Expand All @@ -27,6 +31,8 @@ public function testBootResolveFactory(): void
public function testBindingsFromAllServiceProviders(): void
{
$this->assertBindings($this->app, null, [
RunInTransactionActionContract::class => RunInTransactionAction::class,
SafeUniqueSaveActionContract::class => SafeUniqueSaveAction::class,
GetBasePathForStubsActionContract::class => GetBasePathForStubsAction::class,
GetNamespaceForStubsActionContract::class => GetNamespaceForStubsAction::class,
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Tests\LaraStrict\Unit\Testing\Database\Contracts;

use LaraStrict\Testing\Database\Contracts\RunInTransactionActionContractAssert;
use LaraStrict\Testing\Database\Contracts\RunInTransactionActionContractExpectation;
use PHPUnit\Framework\TestCase;

class RunInTransactionActionContractAssertTest extends TestCase
{
public function testExecuteWithFirstFailed(): void
{
$assert = new RunInTransactionActionContractAssert([
new RunInTransactionActionContractExpectation(true),
new RunInTransactionActionContractExpectation(false, 2),
]);

$count = 0;
$assert->execute(static function () use (&$count) {
++$count;
});

$this->assertEquals(2, $count);
}

public function testExecuteDefaultRunClosureOnce(): void
{
$assert = new RunInTransactionActionContractAssert();

$count = 0;
$assert->execute(static function () use (&$count) {
++$count;
});

$this->assertEquals(1, $count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Tests\LaraStrict\Unit\Testing\Database\Contracts;

use LaraStrict\Testing\Database\Contracts\SafeUniqueSaveActionContractAssert;
use LaraStrict\Testing\Database\Contracts\SafeUniqueSaveActionContractExpectation;
use PHPUnit\Framework\TestCase;
use Tests\LaraStrict\Feature\Database\Models\Test;

class SafeUniqueSaveActionContractAssertTest extends TestCase
{
public function testExecuteOneTry(): void
{
$model = new Test();
$expectations = [new SafeUniqueSaveActionContractExpectation($model)];
$expectedResult = 'test1';

$this->assertExecute($expectations, $model, $expectedResult);
}

public function testExecuteTwoTries(): void
{
$model = new Test();
$expectations = [
new SafeUniqueSaveActionContractExpectation($model, fail: true),
new SafeUniqueSaveActionContractExpectation($model, tries: 2),
];
$expectedResult = 'test2';

$this->assertExecute($expectations, $model, $expectedResult);
}

protected function assertExecute(array $expectations, Test $model, string $expectedResult): void
{
$assert = new SafeUniqueSaveActionContractAssert($expectations);

$calls = 0;
$result = $assert->execute($model, function (Test $model, int $tries) use (&$calls) {
++$calls;
$this->assertEquals($calls, $tries);
return 'test' . $calls;
});

$this->assertEquals($expectedResult, $result);
}
}

0 comments on commit a776d5f

Please sign in to comment.