Skip to content

Commit

Permalink
support unit instance mocking
Browse files Browse the repository at this point in the history
Also prioritises the dispatching of unit instances, now that the preferred way of running units is with the new PHP syntax specifying the arguments by name.
  • Loading branch information
Mulkave committed Apr 13, 2022
1 parent f54a210 commit 46a4bc3
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 8 deletions.
36 changes: 28 additions & 8 deletions src/Bus/UnitDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Lucid\Bus;

use App;
use Lucid\Testing\UnitMock;
use Lucid\Testing\UnitMockRegistry;
use ReflectionClass;
use Lucid\Units\Job;
use ReflectionException;
Expand Down Expand Up @@ -31,22 +34,39 @@ trait UnitDispatcher
*/
public function run($unit, $arguments = [], $extra = [])
{
if ($arguments instanceof Request) {
if (is_object($unit) && !App::runningUnitTests()) {
$result = $this->dispatch($unit);
} elseif ($arguments instanceof Request) {
$result = $this->dispatch($this->marshal($unit, $arguments, $extra));
} else {
if (!is_object($unit)) {
$unit = $this->marshal($unit, new Collection(), $arguments);
}

if ($unit instanceof Operation) {
event(new OperationStarted(get_class($unit), $arguments));
}
// don't dispatch unit when in tests and have a mock for it.
} elseif (App::runningUnitTests() && app(UnitMockRegistry::class)->has(get_class($unit))) {
/** @var UnitMock $mock */
$mock = app(UnitMockRegistry::class)->get(get_class($unit));
$mock->compareTo($unit);

if ($unit instanceof Job) {
event(new JobStarted(get_class($unit), $arguments));
// Reaching this step confirms that the expected mock is similar to the passed instance, so we
// get the unit's mock counterpart to be dispatched. Otherwise, the previous step would
// throw an exception when the mock doesn't match the passed instance.
$unit = $this->marshal(
get_class($unit),
new Collection(),
$mock->getConstructorExpectationsForInstance($unit)
);
}

$result = $this->dispatch($unit, $arguments);
$result = $this->dispatch($unit);
}

if ($unit instanceof Operation) {
event(new OperationStarted(get_class($unit), $arguments));
}

if ($unit instanceof Job) {
event(new JobStarted(get_class($unit), $arguments));
}

return $result;
Expand Down
62 changes: 62 additions & 0 deletions src/Testing/UnitMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,42 @@ public function setConstructorExpectations(array $constructorExpectations)
$this->constructorExpectations[] = $this->currentConstructorExpectations;
}

public function getConstructorExpectations()
{
return $this->constructorExpectations;
}

/**
* Returns constructor expectations array that matches the given $unit.
* Empty array otherwise.
*
* @param $unit
* @return array|mixed|void
* @throws ReflectionException
*/
public function getConstructorExpectationsForInstance($unit)
{
foreach ($this->constructorExpectations as $index => $args) {
$expected = new $unit(...$args);

$ref = new ReflectionClass($unit);

// we start by assuming that the unit instance and the $expected one are equal
// until proven otherwise when we find differences between properties.
$isEqual = true;
foreach ($ref->getProperties() as $property) {
if ($property->getValue($unit) !== $property->getValue($expected)) {
$isEqual = false;
break;
}
}

if ($isEqual) {
return $this->constructorExpectations[$index];
}
}
}

/**
* @return array
* @throws ReflectionException
Expand Down Expand Up @@ -111,6 +147,32 @@ private function registerMock(): UnitMock
return $this;
}

/**
* Compare the mock to an actual instance.
*
* @param object $unit
* @return void
* @throws Mockery\Exception\NoMatchingExpectationException
*/
public function compareTo(object $unit)
{
$expected = array_map(fn($args) => new $unit(...$args), $this->constructorExpectations);

$ref = new ReflectionClass($unit);
foreach ($ref->getProperties() as $property) {

$expectations = array_map(fn($instance) => $property->getValue($instance), $expected);

if (!in_array($property->getValue($unit), $expectations)) {
throw new Mockery\Exception\NoMatchingExpectationException(
"Mismatch in \${$property->getName()} when running {$this->unit} \n\n--- Expected (one of)\n".
print_r(join("\n", array_map(fn($instance) => $property->getValue($instance), $expected)), true).
"\n\n+++Actual:\n".print_r($property->getValue($unit), true)."\n\n"
);
}
}
}

public function getMock(): MockInterface
{
$this->registerMock();
Expand Down

0 comments on commit 46a4bc3

Please sign in to comment.