Execute all parent beforeEach and afterEach functions for each test

This commit is contained in:
jshayes
2024-10-11 23:32:45 -04:00
parent 0c57142c03
commit a6cd83665c
13 changed files with 77 additions and 22 deletions

View File

@ -61,8 +61,10 @@ trait Testable
/** /**
* The test's describing, if any. * The test's describing, if any.
*
* @var string[]
*/ */
public ?string $__describing = null; public array $__describing = [];
/** /**
* Whether the test has ran or not. * Whether the test has ran or not.

View File

@ -31,8 +31,10 @@ final class TestCaseMethodFactory
/** /**
* The test's describing, if any. * The test's describing, if any.
*
* @var string[]
*/ */
public ?string $describing = null; public array $describing = [];
/** /**
* The test's description, if any. * The test's description, if any.
@ -201,7 +203,7 @@ final class TestCaseMethodFactory
]; ];
foreach ($this->depends as $depend) { foreach ($this->depends as $depend) {
$depend = Str::evaluable($this->describing !== null ? Str::describe($this->describing, $depend) : $depend); $depend = Str::evaluable($this->describing === [] ? $depend : Str::describe($this->describing, $depend));
$this->attributes[] = new Attribute( $this->attributes[] = new Attribute(
\PHPUnit\Framework\Attributes\Depends::class, \PHPUnit\Framework\Attributes\Depends::class,

View File

@ -43,7 +43,7 @@ if (! function_exists('beforeAll')) {
*/ */
function beforeAll(Closure $closure): void function beforeAll(Closure $closure): void
{ {
if (! is_null(DescribeCall::describing())) { if (DescribeCall::describing() !== []) {
$filename = Backtrace::file(); $filename = Backtrace::file();
throw new BeforeAllWithinDescribe($filename); throw new BeforeAllWithinDescribe($filename);
@ -205,7 +205,7 @@ if (! function_exists('afterAll')) {
*/ */
function afterAll(Closure $closure): void function afterAll(Closure $closure): void
{ {
if (! is_null(DescribeCall::describing())) { if (DescribeCall::describing() !== []) {
$filename = Backtrace::file(); $filename = Backtrace::file();
throw new AfterAllWithinDescribe($filename); throw new AfterAllWithinDescribe($filename);

View File

@ -54,7 +54,7 @@ final class AfterEachCall
$proxies = $this->proxies; $proxies = $this->proxies;
$afterEachTestCase = ChainableClosure::boundWhen( $afterEachTestCase = ChainableClosure::boundWhen(
fn (): bool => is_null($describing) || $this->__describing === $describing, fn (): bool => $describing === [] || in_array(end($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
)->bindTo($this, self::class); )->bindTo($this, self::class);

View File

@ -63,12 +63,12 @@ final class BeforeEachCall
$beforeEachTestCall = function (TestCall $testCall) use ($describing): void { $beforeEachTestCall = function (TestCall $testCall) use ($describing): void {
if ($this->describing !== null) { if ($this->describing !== []) {
if ($describing !== $this->describing) { if (end($describing) !== end($this->describing)) {
return; return;
} }
if ($describing !== $testCall->describing) { if (! in_array(end($describing), $testCall->describing, true)) {
return; return;
} }
} }
@ -77,7 +77,7 @@ final class BeforeEachCall
}; };
$beforeEachTestCase = ChainableClosure::boundWhen( $beforeEachTestCase = ChainableClosure::boundWhen(
fn (): bool => is_null($describing) || $this->__describing === $describing, fn (): bool => $describing === [] || in_array(end($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
)->bindTo($this, self::class); )->bindTo($this, self::class);
@ -96,7 +96,7 @@ final class BeforeEachCall
*/ */
public function after(Closure $closure): self public function after(Closure $closure): self
{ {
if ($this->describing === null) { if ($this->describing === []) {
throw new AfterBeforeTestFunction($this->filename); throw new AfterBeforeTestFunction($this->filename);
} }

View File

@ -11,11 +11,15 @@ trait Describable
{ {
/** /**
* Note: this is property is not used; however, it gets added automatically by rector php. * Note: this is property is not used; however, it gets added automatically by rector php.
*
* @var string[]
*/ */
public string $__describing; public array $__describing;
/** /**
* The describing of the test case. * The describing of the test case.
*
* @var string[]
*/ */
public ?string $describing = null; public array $describing = [];
} }

View File

@ -39,10 +39,12 @@ final class DescribeCall
/** /**
* What is the current describing. * What is the current describing.
*
* @return string[]
*/ */
public static function describing(): ?string public static function describing(): array
{ {
return self::$describing[count(self::$describing) - 1] ?? null; return self::$describing;
} }
/** /**
@ -73,7 +75,7 @@ final class DescribeCall
if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) { if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) {
$this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename); $this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename);
$this->currentBeforeEachCall->describing = $this->description; $this->currentBeforeEachCall->describing[] = $this->description;
} }
$this->currentBeforeEachCall->{$name}(...$arguments); // @phpstan-ignore-line $this->currentBeforeEachCall->{$name}(...$arguments); // @phpstan-ignore-line

View File

@ -76,7 +76,7 @@ final class TestCall // @phpstan-ignore-line
throw new TestDescriptionMissing($this->filename); throw new TestDescriptionMissing($this->filename);
} }
$description = is_null($this->describing) $description = $this->describing === []
? $this->description ? $this->description
: Str::describe($this->describing, $this->description); : Str::describe($this->describing, $this->description);
@ -683,7 +683,7 @@ final class TestCall // @phpstan-ignore-line
throw new TestDescriptionMissing($this->filename); throw new TestDescriptionMissing($this->filename);
} }
if (! is_null($this->describing)) { if ($this->describing !== []) {
$this->testCaseMethod->describing = $this->describing; $this->testCaseMethod->describing = $this->describing;
$this->testCaseMethod->description = Str::describe($this->describing, $this->description); $this->testCaseMethod->description = Str::describe($this->describing, $this->description);
} else { } else {

View File

@ -103,10 +103,14 @@ final class Str
/** /**
* Creates a describe block as `$describeDescription` → `$testDescription` format. * Creates a describe block as `$describeDescription` → `$testDescription` format.
*
* @param string[] $describeDescriptions
*/ */
public static function describe(string $describeDescription, string $testDescription): string public static function describe(array $describeDescriptions, string $testDescription): string
{ {
return sprintf('`%s` → %s', $describeDescription, $testDescription); $descriptionComponents = [...$describeDescriptions, $testDescription];
return sprintf(str_repeat('`%s` → ', count($describeDescriptions)).'%s', ...$descriptionComponents);
} }
/** /**

View File

@ -27,6 +27,8 @@
PASS Tests\Features\AfterEach PASS Tests\Features\AfterEach
✓ it does not get executed before the test ✓ it does not get executed before the test
✓ it gets executed after the test ✓ it gets executed after the test
✓ outer → inner → it does not get executed before the test
✓ outer → inner → it should call all parent afterEach functions
PASS Tests\Features\Assignee PASS Tests\Features\Assignee
✓ it may be associated with an assignee [@nunomaduro, @taylorotwell] ✓ it may be associated with an assignee [@nunomaduro, @taylorotwell]
@ -40,6 +42,7 @@
PASS Tests\Features\BeforeEach PASS Tests\Features\BeforeEach
✓ it gets executed before each test ✓ it gets executed before each test
✓ it gets executed before each test once again ✓ it gets executed before each test once again
✓ outer → inner → it should call all parent beforeEach functions
PASS Tests\Features\BeforeEachProxiesToTestCallWithExpectations PASS Tests\Features\BeforeEachProxiesToTestCallWithExpectations
✓ runs 1 ✓ runs 1
@ -1585,4 +1588,4 @@
WARN Tests\Visual\Version WARN Tests\Visual\Version
- visual snapshot of help command output - visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1096 passed (2649 assertions) Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1099 passed (2656 assertions)

View File

@ -26,3 +26,25 @@ it('gets executed after the test', function () {
afterEach(function () { afterEach(function () {
$this->state->bar = 2; $this->state->bar = 2;
}); });
describe('outer', function () {
afterEach(function () {
$this->state->bar++;
});
describe('inner', function () {
afterEach(function () {
$this->state->bar++;
});
it('does not get executed before the test', function () {
expect($this->state)->toHaveProperty('bar');
expect($this->state->bar)->toBe(2);
});
it('should call all parent afterEach functions', function () {
expect($this->state)->toHaveProperty('bar');
expect($this->state->bar)->toBe(4);
});
});
});

View File

@ -25,3 +25,19 @@ it('gets executed before each test once again', function () {
beforeEach(function () { beforeEach(function () {
$this->bar++; $this->bar++;
}); });
describe('outer', function () {
beforeEach(function () {
$this->bar++;
});
describe('inner', function () {
beforeEach(function () {
$this->bar++;
});
it('should call all parent beforeEach functions', function () {
expect($this->bar)->toBe(3);
});
});
});

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) expect($run('--exclude-group=integration'))
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1086 passed (2625 assertions)') ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1089 passed (2632 assertions)')
->toContain('Parallel: 3 processes'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();