Skip to content

Commit 28edd2f

Browse files
committed
Adds let/get methods
1 parent f1f72d0 commit 28edd2f

File tree

11 files changed

+237
-225
lines changed

11 files changed

+237
-225
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ coverage.xml
99
.temp/coverage.php
1010
*.swp
1111
*.swo
12+
.phpunit.cache/*

README.md

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,55 @@
1-
# Given When Then (GWT) Plugin for Pest
1+
# PSpec
22

3-
> A simple API allows you to structure your tests focused on the behaviour. Given-When-Then separation makes the test easier to understand at a glance.
3+
> PSpec is a Pest plugin for composing multi scenarios tests with a simple API, based on RSpec let.
44
55
### Install
66
```shell
7-
composer require milroyfraser/pest-plugin-gwt --dev
7+
composer require cfx/pspec --dev
88
```
99

10-
### Usage
10+
### Simple usage
1111
```php
12-
use App\Exceptions\BlockedUserException;
13-
use App\Models\User;
14-
use function Pest\Gwt\scenario;
15-
use function Pest\Laravel\assertDatabaseHas;
16-
17-
scenario('activate user')
18-
->given(fn() => User::factory()->create())
19-
->when(fn(User $user) => $user->activate())
20-
->then(fn(User $user) => assertDatabaseHas('users', [
21-
'id' => $user->id,
22-
'activated' => true,
23-
]));
24-
25-
scenario('activate blocked user')
26-
->given(fn() => User::factory()->blocked()->create())
27-
->when(fn(User $user) => $user->activate())
28-
->throws(BlockedUserException::class);
29-
```
12+
use function Cfx\PSpec\context;
13+
use function Cfx\PSpec\expectSubject;
14+
use function Cfx\PSpec\get;
15+
use function Cfx\PSpec\let;
16+
use function Cfx\PSpec\subject;
3017

31-
[more examples](https://github.com/milroyfraser/pest-plugin-gwt/blob/master/tests/Example.php)
18+
subject(fn () => User::factory()->create(['is_admin' => get('is_admin')]));
3219

20+
context('when is admin', function () {
21+
let('is_admin', fn() => true);
3322

34-
**Given** a state
23+
it('returns true', function () {
24+
expectSubject()->is_admin->toBeTrue();
25+
});
26+
});
3527

36-
Given method accepts a `Closure`. This is where we `Arrange` application state. The return values will become argument/s of the `when` closure.
28+
context('when is not admin', function () {
29+
let('is_admin', fn() => false);
3730

38-
**When** I do something
31+
it('returns false', function () {
32+
expectSubject()->is_admin->toBeFalse();
33+
});
34+
});
35+
```
3936

40-
When method accepts a `Closure`. This is where we `Act` (perform) the operation. The return values will become argument/s of the `then` closure.
37+
### Higher order testing
4138

42-
**Then** I expect an outcome
39+
```php
40+
use function Cfx\PSpec\context;
41+
use function Cfx\PSpec\getSubject;
42+
use function Cfx\PSpec\let;
4343

44-
Then method accepts a `Closure`. This is where we `Assert` the outcome.
44+
context('when using high order testing', function () {
45+
let('param2', fn () => 2);
46+
47+
it('can use high order testing')
48+
->expect(getSubject(...))
49+
->toEqual(2);
50+
});
51+
```
4552

46-
> If you want to start testing your application with Pest, visit the main **[Pest Repository](https://github.com/pestphp/pest)**.
53+
[more examples](https://github.com/coderfoxbrasil/cfx-pspec/blob/master/tests/Example.php)
4754

48-
- Explore the docs: **[pestphp.com/docs/plugins/creating-plugins »](https://pestphp.com/docs/plugins/creating-plugins)**
49-
- Follow us on Twitter: **[@pestphp »](https://twitter.com/pestphp)**
50-
- Join us on the Discord Server: **[discord.gg/bMAJv82 »](https://discord.gg/bMAJv82)**
5155

52-
Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** under the **[Sponsorware license](https://github.com/sponsorware/docs)**. It got open-sourced and is now licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.

composer.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "milroyfraser/pest-plugin-gwt",
3-
"description": "Given When Then(GWT) Plugin for Pest",
2+
"name": "cfx/pspec",
3+
"description": "Let lazy evaluation helpers you in your Pest tests",
44
"keywords": [
55
"php",
66
"framework",
@@ -10,7 +10,11 @@
1010
"testing",
1111
"plugin",
1212
"AAA",
13-
"GWT"
13+
"given",
14+
"let",
15+
"lazy evaluation",
16+
"rspec",
17+
"bdd"
1418
],
1519
"license": "MIT",
1620
"require": {
@@ -20,7 +24,7 @@
2024
},
2125
"autoload": {
2226
"psr-4": {
23-
"Pest\\Gwt\\": "src/"
27+
"Cfx\\PSpec\\": "src/"
2428
},
2529
"files": [
2630
"src/Autoload.php"

phpstan.neon

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,3 @@ parameters:
1111
checkMissingIterableValueType: true
1212
checkGenericClassInNonGenericObjectType: false
1313
reportUnmatchedIgnoredErrors: true
14-
15-
ignoreErrors:
16-
- "#has parameter \\$exceptionMessage with null as default value.#"

src/Autoload.php

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,53 @@
22

33
declare(strict_types=1);
44

5-
namespace Pest\Gwt;
5+
namespace Cfx\PSpec;
66

7-
/**
8-
* @return BehaviorDescriptor
9-
*/
10-
function scenario(string $description)
7+
use Cfx\PSpec\Concern\HasLetVariables;
8+
use Closure;
9+
use Pest\Expectation;
10+
use Pest\PendingCalls\BeforeEachCall;
11+
use Pest\PendingCalls\DescribeCall;
12+
use Pest\Plugin;
13+
use Pest\Support\Backtrace;
14+
use Pest\TestSuite;
15+
16+
Plugin::uses(HasLetVariables::class);
17+
18+
function subject(Closure $subject): void
19+
{
20+
new SubjectTester($subject);
21+
}
22+
23+
function let(string $key, Closure $resolver): BeforeEachCall
24+
{
25+
$filename = Backtrace::testFile();
26+
27+
return new BeforeEachCall(
28+
TestSuite::getInstance(),
29+
$filename,
30+
fn () => SubjectTester::getInstance()->let($key, $resolver),
31+
);
32+
}
33+
34+
function get(string $key): mixed
35+
{
36+
return SubjectTester::getInstance()->get($key);
37+
}
38+
39+
function context(string $description, Closure $tests): DescribeCall
40+
{
41+
return SubjectTester::getInstance()->context($description, $tests);
42+
}
43+
44+
function getSubject(): mixed
45+
{
46+
$tester = SubjectTester::getInstance();
47+
48+
return $tester->resolveSubject();
49+
}
50+
51+
function expectSubject(): Expectation
1152
{
12-
return new BehaviorDescriptor($description);
53+
return expect(getSubject());
1354
}

src/BehaviorDescriptor.php

Lines changed: 0 additions & 92 deletions
This file was deleted.

src/Concern/HasLetVariables.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cfx\PSpec\Concern;
6+
7+
use Closure;
8+
9+
trait HasLetVariables
10+
{
11+
/**
12+
* @var array<string, array{'resolved': bool, 'value': mixed, 'resolver': Closure}>
13+
*/
14+
private array $variables = [];
15+
16+
public function let(string $key, Closure $resolver): self
17+
{
18+
$this->setVariable($key, $resolver);
19+
20+
return $this;
21+
}
22+
23+
public function get(string $key): mixed
24+
{
25+
return $this->getVariableValue($key);
26+
}
27+
28+
private function getVariableValue(string $key): mixed
29+
{
30+
if (! array_key_exists($key, $this->variables)) {
31+
/** @phpstan-ignore-next-line */
32+
throw new \Exception("Attempt to read $key, when was not set");
33+
}
34+
35+
if ($this->variables[$key]['resolved']) {
36+
return $this->variables[$key]['value'];
37+
}
38+
39+
return $this->resolveVariable($key);
40+
}
41+
42+
private function setVariable(string $key, Closure $resolver): void
43+
{
44+
$this->variables[$key] = [
45+
'resolved' => false,
46+
'resolver' => $resolver,
47+
'value' => null,
48+
];
49+
}
50+
51+
private function resolveVariable(string $key): mixed
52+
{
53+
$value = $this->variables[$key]['resolver']();
54+
55+
$this->variables[$key]['value'] = $value;
56+
$this->variables[$key]['resolved'] = true;
57+
58+
return $value;
59+
}
60+
}

src/Plugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace Pest\Gwt;
5+
namespace Cfx\PSpec;
66

77
// use Pest\Contracts\Plugins\AddsOutput;
88
// use Pest\Contracts\Plugins\HandlesArguments;

src/SubjectTester.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cfx\PSpec;
6+
7+
use Cfx\PSpec\Concern\HasLetVariables;
8+
use Closure;
9+
use Pest\PendingCalls\DescribeCall;
10+
11+
final class SubjectTester
12+
{
13+
use HasLetVariables;
14+
15+
private static ?SubjectTester $instance = null;
16+
17+
public function __construct(
18+
protected Closure $subjectResolver,
19+
) {
20+
self::$instance = $this;
21+
}
22+
23+
public static function getInstance(): SubjectTester
24+
{
25+
return self::$instance ?? throw new \Exception('No subject test instance found, did you called subject()');
26+
}
27+
28+
public function context(string $context, Closure $tests): DescribeCall
29+
{
30+
return describe($context, function () use ($tests) {
31+
return $tests();
32+
});
33+
}
34+
35+
public function resolveSubject(): mixed
36+
{
37+
$subject = $this->subjectResolver;
38+
39+
return $subject();
40+
}
41+
}

0 commit comments

Comments
 (0)