Skip to content

Commit

Permalink
Adds let/get methods
Browse files Browse the repository at this point in the history
  • Loading branch information
hjJunior committed Apr 17, 2024
1 parent f1f72d0 commit 28edd2f
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 225 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ coverage.xml
.temp/coverage.php
*.swp
*.swo
.phpunit.cache/*
71 changes: 37 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
# Given When Then (GWT) Plugin for Pest
# PSpec

> 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.
> PSpec is a Pest plugin for composing multi scenarios tests with a simple API, based on RSpec let.
### Install
```shell
composer require milroyfraser/pest-plugin-gwt --dev
composer require cfx/pspec --dev
```

### Usage
### Simple usage
```php
use App\Exceptions\BlockedUserException;
use App\Models\User;
use function Pest\Gwt\scenario;
use function Pest\Laravel\assertDatabaseHas;

scenario('activate user')
->given(fn() => User::factory()->create())
->when(fn(User $user) => $user->activate())
->then(fn(User $user) => assertDatabaseHas('users', [
'id' => $user->id,
'activated' => true,
]));

scenario('activate blocked user')
->given(fn() => User::factory()->blocked()->create())
->when(fn(User $user) => $user->activate())
->throws(BlockedUserException::class);
```
use function Cfx\PSpec\context;
use function Cfx\PSpec\expectSubject;
use function Cfx\PSpec\get;
use function Cfx\PSpec\let;
use function Cfx\PSpec\subject;

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

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

**Given** a state
it('returns true', function () {
expectSubject()->is_admin->toBeTrue();
});
});

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

**When** I do something
it('returns false', function () {
expectSubject()->is_admin->toBeFalse();
});
});
```

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

**Then** I expect an outcome
```php
use function Cfx\PSpec\context;
use function Cfx\PSpec\getSubject;
use function Cfx\PSpec\let;

Then method accepts a `Closure`. This is where we `Assert` the outcome.
context('when using high order testing', function () {
let('param2', fn () => 2);

it('can use high order testing')
->expect(getSubject(...))
->toEqual(2);
});
```

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

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

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)**.
12 changes: 8 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "milroyfraser/pest-plugin-gwt",
"description": "Given When Then(GWT) Plugin for Pest",
"name": "cfx/pspec",
"description": "Let lazy evaluation helpers you in your Pest tests",
"keywords": [
"php",
"framework",
Expand All @@ -10,7 +10,11 @@
"testing",
"plugin",
"AAA",
"GWT"
"given",
"let",
"lazy evaluation",
"rspec",
"bdd"
],
"license": "MIT",
"require": {
Expand All @@ -20,7 +24,7 @@
},
"autoload": {
"psr-4": {
"Pest\\Gwt\\": "src/"
"Cfx\\PSpec\\": "src/"
},
"files": [
"src/Autoload.php"
Expand Down
3 changes: 0 additions & 3 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,3 @@ parameters:
checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: false
reportUnmatchedIgnoredErrors: true

ignoreErrors:
- "#has parameter \\$exceptionMessage with null as default value.#"
53 changes: 47 additions & 6 deletions src/Autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,53 @@

declare(strict_types=1);

namespace Pest\Gwt;
namespace Cfx\PSpec;

/**
* @return BehaviorDescriptor
*/
function scenario(string $description)
use Cfx\PSpec\Concern\HasLetVariables;
use Closure;
use Pest\Expectation;
use Pest\PendingCalls\BeforeEachCall;
use Pest\PendingCalls\DescribeCall;
use Pest\Plugin;
use Pest\Support\Backtrace;
use Pest\TestSuite;

Plugin::uses(HasLetVariables::class);

function subject(Closure $subject): void
{
new SubjectTester($subject);
}

function let(string $key, Closure $resolver): BeforeEachCall
{
$filename = Backtrace::testFile();

return new BeforeEachCall(
TestSuite::getInstance(),
$filename,
fn () => SubjectTester::getInstance()->let($key, $resolver),
);
}

function get(string $key): mixed
{
return SubjectTester::getInstance()->get($key);
}

function context(string $description, Closure $tests): DescribeCall
{
return SubjectTester::getInstance()->context($description, $tests);
}

function getSubject(): mixed
{
$tester = SubjectTester::getInstance();

return $tester->resolveSubject();
}

function expectSubject(): Expectation
{
return new BehaviorDescriptor($description);
return expect(getSubject());
}
92 changes: 0 additions & 92 deletions src/BehaviorDescriptor.php

This file was deleted.

60 changes: 60 additions & 0 deletions src/Concern/HasLetVariables.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Cfx\PSpec\Concern;

use Closure;

trait HasLetVariables
{
/**
* @var array<string, array{'resolved': bool, 'value': mixed, 'resolver': Closure}>
*/
private array $variables = [];

public function let(string $key, Closure $resolver): self
{
$this->setVariable($key, $resolver);

return $this;
}

public function get(string $key): mixed
{
return $this->getVariableValue($key);
}

private function getVariableValue(string $key): mixed
{
if (! array_key_exists($key, $this->variables)) {
/** @phpstan-ignore-next-line */
throw new \Exception("Attempt to read $key, when was not set");
}

if ($this->variables[$key]['resolved']) {
return $this->variables[$key]['value'];
}

return $this->resolveVariable($key);
}

private function setVariable(string $key, Closure $resolver): void
{
$this->variables[$key] = [
'resolved' => false,
'resolver' => $resolver,
'value' => null,
];
}

private function resolveVariable(string $key): mixed
{
$value = $this->variables[$key]['resolver']();

$this->variables[$key]['value'] = $value;
$this->variables[$key]['resolved'] = true;

return $value;
}
}
2 changes: 1 addition & 1 deletion src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Pest\Gwt;
namespace Cfx\PSpec;

// use Pest\Contracts\Plugins\AddsOutput;
// use Pest\Contracts\Plugins\HandlesArguments;
Expand Down
41 changes: 41 additions & 0 deletions src/SubjectTester.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Cfx\PSpec;

use Cfx\PSpec\Concern\HasLetVariables;
use Closure;
use Pest\PendingCalls\DescribeCall;

final class SubjectTester
{
use HasLetVariables;

private static ?SubjectTester $instance = null;

public function __construct(
protected Closure $subjectResolver,
) {
self::$instance = $this;
}

public static function getInstance(): SubjectTester
{
return self::$instance ?? throw new \Exception('No subject test instance found, did you called subject()');
}

public function context(string $context, Closure $tests): DescribeCall
{
return describe($context, function () use ($tests) {
return $tests();
});
}

public function resolveSubject(): mixed
{
$subject = $this->subjectResolver;

return $subject();
}
}
Loading

0 comments on commit 28edd2f

Please sign in to comment.