test: FakeChangeLog and SchemaFixture instead of repeated mocks (T2)
- tests/Support/FakeChangeLog extends ChangeLog: records record()/recordPartial() calls, increasing ids, optional failure; new FakeChangeLog(id) / partialsOnly() / any(id) / forbidden() mirror what the replaced mocks allowed. assertRecorded() / assertPartials() check count and the named arguments. Calls a fake does not allow throw and are also verified in TestCase::tearDown (one assertion per test that uses a fake), so swallowed change-log calls still fail like a never() expectation. - All ChangeLog mocks of ability/integration tests replaced (the admin tests that stub query/purge/clear keep their mocks); expectException tests that checked the partial log now catch and assert. - SchemaFixture::managed(?divi) replaces the 21 isManaged-only ContentSchema mocks; DiviFixtures::schema() uses it. Tests 1397 -> 1404 (FakeChangeLogTest); no test has fewer assertions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,6 @@ use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystem;
|
||||
use LIT\MCP\Integrations\Divi\Schema\ModuleCatalog;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use Mockery;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
@@ -41,11 +40,7 @@ final class DiviFixtures
|
||||
|
||||
public static function schema(bool $diviEnabled = true): ContentSchema
|
||||
{
|
||||
$schema = Mockery::mock(ContentSchema::class);
|
||||
$schema->allows('isManaged')->andReturn(true);
|
||||
$schema->allows('diviEnabled')->andReturn($diviEnabled);
|
||||
|
||||
return $schema;
|
||||
return SchemaFixture::managed($diviEnabled);
|
||||
}
|
||||
|
||||
/** Shapes as GlobalData / GlobalPreset return them (Divi 5.2.1, verified in DDEV). */
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Support;
|
||||
|
||||
use LIT\MCP\ChangeLog\ChangeLog;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviComposition;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
||||
@@ -83,13 +82,10 @@ final class DiviWriterFixture
|
||||
return $saver;
|
||||
}
|
||||
|
||||
/** A ChangeLog that accepts every record (log id 4711). */
|
||||
public static function log(): ChangeLog
|
||||
/** A ChangeLog that accepts records (log ids from 4711). */
|
||||
public static function log(): FakeChangeLog
|
||||
{
|
||||
$log = Mockery::mock(ChangeLog::class);
|
||||
$log->allows('record')->andReturn(4711);
|
||||
|
||||
return $log;
|
||||
return new FakeChangeLog(4711);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Support;
|
||||
|
||||
use LIT\MCP\ChangeLog\ChangeLog;
|
||||
use LogicException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* ChangeLog without a database. Allowed calls are kept in $records / $partials (arguments by
|
||||
* name); record() returns increasing ids from $firstId, or throws $failure after noting the call.
|
||||
*
|
||||
* What a fake allows mirrors the Mockery mocks it replaces:
|
||||
* - new FakeChangeLog(id) record() only (allows('record')->andReturn(id))
|
||||
* - FakeChangeLog::partialsOnly() recordPartial() only (expects('recordPartial'))
|
||||
* - FakeChangeLog::any(id) both
|
||||
* - FakeChangeLog::forbidden() nothing (a mock without expectations)
|
||||
* A call that is not allowed throws – and, because the code under test often swallows
|
||||
* change-log failures (FollowUp), it is also noted and fails the test in TestCase::tearDown()
|
||||
* via verify().
|
||||
*/
|
||||
class FakeChangeLog extends ChangeLog
|
||||
{
|
||||
/**
|
||||
* @var list<array{ability: string, object_type: string, object_id: string, summary: string,
|
||||
* revision_id: ?int, snapshot_id: ?string, result: string}>
|
||||
*/
|
||||
public array $records = [];
|
||||
|
||||
/**
|
||||
* @var list<array{ability: string, object_type: string, object_id: string, summary: string,
|
||||
* revision_id: ?int}>
|
||||
*/
|
||||
public array $partials = [];
|
||||
|
||||
/** @var list<string> Calls that were not allowed, across all fakes of the running test. */
|
||||
private static array $unexpected = [];
|
||||
|
||||
private static bool $used = false;
|
||||
|
||||
public function __construct(
|
||||
private int $firstId = 1,
|
||||
private ?Throwable $failure = null,
|
||||
private bool $allowsRecord = true,
|
||||
private bool $allowsPartial = false
|
||||
) {
|
||||
parent::__construct();
|
||||
self::$used = true;
|
||||
}
|
||||
|
||||
public static function forbidden(): self
|
||||
{
|
||||
return new self(1, null, false, false);
|
||||
}
|
||||
|
||||
public static function partialsOnly(): self
|
||||
{
|
||||
return new self(1, null, false, true);
|
||||
}
|
||||
|
||||
public static function any(int $firstId = 1): self
|
||||
{
|
||||
return new self($firstId, null, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails the running test if any fake got a call it does not allow; resets for the next test.
|
||||
* Counts as one assertion in tests that created a fake (like a Mockery never() expectation).
|
||||
*/
|
||||
public static function verify(): void
|
||||
{
|
||||
$unexpected = self::$unexpected;
|
||||
$used = self::$used;
|
||||
self::$unexpected = [];
|
||||
self::$used = false;
|
||||
if ($used || $unexpected !== []) {
|
||||
Assert::assertSame([], $unexpected, 'Unexpected change log calls: ' . implode('; ', $unexpected));
|
||||
}
|
||||
}
|
||||
|
||||
public function record(
|
||||
string $ability,
|
||||
string $objectType,
|
||||
string $objectId,
|
||||
string $summary,
|
||||
?int $revisionId = null,
|
||||
?string $snapshotId = null,
|
||||
string $result = 'ok'
|
||||
): ?int {
|
||||
$this->refuseUnless($this->allowsRecord, sprintf('record(%s, %s, %s)', $ability, $objectType, $objectId));
|
||||
$this->records[] = [
|
||||
'ability' => $ability,
|
||||
'object_type' => $objectType,
|
||||
'object_id' => $objectId,
|
||||
'summary' => $summary,
|
||||
'revision_id' => $revisionId,
|
||||
'snapshot_id' => $snapshotId,
|
||||
'result' => $result,
|
||||
];
|
||||
if ($this->failure !== null) {
|
||||
throw $this->failure;
|
||||
}
|
||||
|
||||
return $this->firstId + count($this->records) - 1;
|
||||
}
|
||||
|
||||
public function recordPartial(
|
||||
string $ability,
|
||||
string $objectType,
|
||||
string $objectId,
|
||||
string $summary,
|
||||
?int $revisionId = null
|
||||
): void {
|
||||
$this->refuseUnless(
|
||||
$this->allowsPartial,
|
||||
sprintf('recordPartial(%s, %s, %s)', $ability, $objectType, $objectId)
|
||||
);
|
||||
$this->partials[] = [
|
||||
'ability' => $ability,
|
||||
'object_type' => $objectType,
|
||||
'object_id' => $objectId,
|
||||
'summary' => $summary,
|
||||
'revision_id' => $revisionId,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the record() calls: one $expected per call, in order; each names only the
|
||||
* arguments it checks (ability, object_type, object_id, summary, revision_id, …).
|
||||
*
|
||||
* @param array<string, mixed> ...$expected
|
||||
*/
|
||||
public function assertRecorded(array ...$expected): void
|
||||
{
|
||||
self::assertCalls('record()', $expected, $this->records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the recordPartial() calls like assertRecorded().
|
||||
*
|
||||
* @param array<string, mixed> ...$expected
|
||||
*/
|
||||
public function assertPartials(array ...$expected): void
|
||||
{
|
||||
self::assertCalls('recordPartial()', $expected, $this->partials);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $expected
|
||||
* @param list<array<string, mixed>> $actual
|
||||
*/
|
||||
private static function assertCalls(string $method, array $expected, array $actual): void
|
||||
{
|
||||
Assert::assertCount(count($expected), $actual, 'Number of ChangeLog::' . $method . ' calls');
|
||||
foreach ($expected as $i => $call) {
|
||||
$checked = [];
|
||||
foreach (array_keys($call) as $key) {
|
||||
Assert::assertArrayHasKey($key, $actual[$i], 'Unknown ChangeLog argument');
|
||||
$checked[$key] = $actual[$i][$key];
|
||||
}
|
||||
Assert::assertSame($call, $checked, 'ChangeLog::' . $method . ' call ' . $i);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return list<string> The summaries of record(), in order. */
|
||||
public function summaries(): array
|
||||
{
|
||||
return array_column($this->records, 'summary');
|
||||
}
|
||||
|
||||
private function refuseUnless(bool $allowed, string $call): void
|
||||
{
|
||||
if (! $allowed) {
|
||||
self::$unexpected[] = $call;
|
||||
throw new LogicException('Unexpected ChangeLog::' . $call);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Support;
|
||||
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
/** ContentSchema stand-ins for ability tests. */
|
||||
final class SchemaFixture
|
||||
{
|
||||
/**
|
||||
* Every post type is managed. $divi answers diviEnabled(); null = the test does not expect
|
||||
* that question (a call fails the test, as with a plain mock).
|
||||
*
|
||||
* @return ContentSchema&MockInterface
|
||||
*/
|
||||
public static function managed(?bool $divi = null): ContentSchema
|
||||
{
|
||||
$schema = Mockery::mock(ContentSchema::class);
|
||||
$schema->allows('isManaged')->andReturn(true);
|
||||
if ($divi !== null) {
|
||||
$schema->allows('diviEnabled')->andReturn($divi);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user