Skip to content

TestSetterAndGetterTrait: Allow setting target per individual test #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 71 additions & 16 deletions src/TestCase/TestSetterAndGetterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Cross\TestUtils\TestCase;

use Cross\TestUtils\Exception\InvalidUsageException;
use Cross\TestUtils\Utils\Instance;
use Cross\TestUtils\Utils\Target;

/**
Expand All @@ -36,6 +37,15 @@
*
* Available keys in the <spec> array:
*
* * 'target': Allows to specify a SUT for this particular test only.
* The value must be either an object a string representing a FQCN
* or an array [FQCN, arg, ...]
*
* * target_callback': Get the SUT via a callback.
* If a string is given, it is assumed that a method
* in the TestCase is meant.
* The callbakc must return an object.
*
* * 'value': The value to test the setter and getter with.
* First the setter will be called with the value as argument.
* Then the assertion will gets called, passing in the value and
Expand Down Expand Up @@ -88,6 +98,8 @@
* @property array $setterAndGetter
*
* @author Mathias Gelhausen <[email protected]>
*
* @since @#next#@ Allow SUT per individual test.
*/
trait TestSetterAndGetterTrait
{
Expand Down Expand Up @@ -123,16 +135,9 @@ public function testSetterAndGetter($name, $spec = null): void
return;
}

$target = Target::get(
$this,
['getSetterAndGetterTarget', 'getTarget'],
['setterAndGetterTarget', 'target'],
'setterAndGetter',
true
);

$spec = $this->setterAndGetterNormalizeSpec($spec, $name, $target);
$value = $spec['value'];
$target = $this->setterAndGetterGetTarget($spec);
$spec = $this->setterAndGetterNormalizeSpec($spec, $name, $target);
$value = $spec['value'];

if ($spec['exception']) {
if (is_array($spec['exception'])) {
Expand Down Expand Up @@ -171,9 +176,53 @@ public function testSetterAndGetter($name, $spec = null): void
}
}

/**
* @param string|array $spec
* @internal
*/
private function setterAndGetterGetTarget($spec): object
{
if (isset($spec['target'])) {
return
is_object($spec['target'])
? $spec['target']
: Instance::withMappedArguments($spec['target'], $this)
;
}

if (isset($spec['target_callback'])) {
$cb = $spec['target_callback'];

if (is_string($cb) && !is_callable($cb)) {
$cb = [$this, $cb];
}

if (!is_callable($cb)) {
throw InvalidUsageException::fromTrait(__TRAIT__, __CLASS__, 'Invalid target callback.');
}

$target = $cb();

if (!is_object($target)) {
throw InvalidUsageException::fromTrait(__TRAIT__, __CLASS__, 'Target callback must return an object.');
}

return $target;
}

return Target::get(
$this,
['getSetterAndGetterTarget', 'getTarget'],
['setterAndGetterTarget', 'target'],
'setterAndGetter',
true
);
}

/**
* Normalize the test specification.
*
* @internal
* @param array|string $spec
* @param string $name
* @param object $target
Expand All @@ -198,10 +247,8 @@ private function setterAndGetterNormalizeSpec($spec, string $name, object $targe
return $normalized;
}

$err = __TRAIT__ . ': ' . get_class($this) . ': ';

if (!is_array($spec)) {
throw new \PHPUnit\Framework\Exception($err . 'Invalid specification. Must be array.');
throw InvalidUsageException::fromTrait(__TRAIT__, __CLASS__, 'Invalid specification. Must be array.');
}

foreach ($spec as $key => $value) {
Expand Down Expand Up @@ -253,12 +300,16 @@ private function setterAndGetterNormalizeSpec($spec, string $name, object $targe
case 'value_callback':
case 'setter_value_callback':
case 'expect_callback':
if (is_string($value)) {
if (is_string($value) && !is_callable($value)) {
$value = [$this, $value];
}

if (!is_callable($value)) {
throw new \PHPUnit\Framework\Exception($err . 'Invalid callback for "' . $key . '".');
throw InvalidUsageException::fromTrait(
__TRAIT__,
__CLASS__,
'Invalid callback for "' . $key . '".'
);
}

$key = substr($key, 0, -9);
Expand All @@ -277,7 +328,11 @@ private function setterAndGetterNormalizeSpec($spec, string $name, object $targe
}

if (!is_callable($value)) {
throw new \PHPUnit\Framework\Exception($err . 'Invalid callback for "' . $key . '".');
throw InvalidUsageException::fromTrait(
__TRAIT__,
__CLASS__,
'Invalid callback for "' . $key . '".'
);
}

break;
Expand Down
106 changes: 105 additions & 1 deletion test/TestUtilsTest/TestCase/TestSetterAndGetterTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,110 @@ public function __call($method, $args)

}

public function provideIndividualTargetData()
{
$obj = new \stdClass;
$obj2 = new class ('', '') {
public $arg1;
public $arg2;
public function __construct($arg1, $arg2)
{
$this->arg1 = $arg1;
$this->arg2 = $arg2;
}
};

return [
[
['target' => \stdClass::class],
\stdClass::class
],
[
['target' => $obj],
$obj
],
[
['target' => [get_class($obj2), 'arg1', 'arg2']],
[get_class($obj2), ['arg1' => 'arg1', 'arg2' => 'arg2']]
],

[
['target_callback' => function () use ($obj) { return $obj; }],
$obj,
],
[
['target_callback' => 'getSut'],
\stdClass::class,
],
];
}

/**
* @dataProvider provideIndividualTargetData
*/
public function testAllowsProvidingIndividualTarget(array $spec, $expect): void
{
$target = new class {
use TestSetterAndGetterTrait {
TestSetterAndGetterTrait::setterAndGetterGetTarget as originalGetTarget;
}

public $sut;

public function testSetterAndGetter($name, $spec=null) {
$this->testSetterAndGetterGetTarget($spec);
}

private function testSetterAndGetterGetTarget($spec) {
$this->sut = $this->originalGetTarget($spec);
}

public function getSut() {
return new \stdClass;
}
};

$target->testSetterAndGetter('test', $spec);

if (is_object($expect)) {
static::assertSame($target->sut, $expect);
} elseif (is_array($expect)) {
static::assertInstanceOf($expect[0], $target->sut);
foreach ($expect[1] as $name => $value) {
static::assertEquals($value, $target->sut->$name);
}
} else {
static::assertInstanceOf($expect, $target->sut);
}
}

public function testSpecifyInvalidTargetCallbackThrowsException()
{
$target = new class {
use TestSetterAndGetterTrait;
};

$this->expectException(InvalidUsageException::class);
$this->expectExceptionMessage('Invalid target callback');

$target->testSetterAndGetter('test', ['target_callback' => 'invalidCallback']);
}

public function testAssureTargetCallbackReturnsObject()
{
$target = new class {
use TestSetterAndGetterTrait;

public function sut() {
return 'not an object';
}
};

$this->expectException(InvalidUsageException::class);
$this->expectExceptionMessage('must return an object');

$target->testSetterAndGetter('test', ['target_callback' => 'sut']);
}

public function normalizationData() : array
{
Expand Down Expand Up @@ -213,7 +317,7 @@ public function callback() {}
};

if (is_string($expect)) {
$this->expectException(\PHPUnit\Framework\Exception::class);
$this->expectException(InvalidUsageException::class);
$this->expectExceptionMessage($expect);
}

Expand Down