Skip to content
Open
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,19 @@ php artisan essentials:pint {--force} {--backup}
- `--force` - Overwrites the existing configuration file without asking for confirmation.
- `--backup` - Creates a backup of the existing configuration file.

#### `essentials:pest`

Adds opinionated defaults to your Pest configuration. The command ensures your tests are more reliable and predictable by:

- Preventing stray HTTP requests
- Normalizing string and UUID generation
- Faking sleep operations
- Freezing time during tests
- Configuring test paths for Feature and Unit tests

```bash
php artisan essentials:pest
```

## Configuration

Expand Down
73 changes: 73 additions & 0 deletions src/Commands/EssentialsPestCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace NunoMaduro\Essentials\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

final class EssentialsPestCommand extends Command
{
private const string OPENING_TAG = 'added with Essentials Script';

private const string CLOSING_TAG = 'end: '.self::OPENING_TAG;

protected $signature = 'essentials:pest';

protected $description = 'Add essential Pest configuration to your tests';

public function handle(): int
{
$pestFile = base_path('tests/Pest.php');

if (! File::exists($pestFile)) {
$this->error('🐞 Show some love to Pest! For Nuno\'s sake, install it first! 🚀');
$this->line('Run: <fg=gray>composer require pestphp/pest --dev && php artisan pest:install</>');

return Command::FAILURE;
}

$content = File::get($pestFile);

if (str_contains($content, self::OPENING_TAG)) {
$this->info('Essential Pest configuration already added.');

return Command::INVALID;
}

$stubPath = $this->getStub();

$stubContent = File::get($stubPath);

$newConfig = PHP_EOL.'// '.self::OPENING_TAG.PHP_EOL;
$newConfig .= $stubContent;
$newConfig .= PHP_EOL.'// '.self::CLOSING_TAG.PHP_EOL;

File::append($pestFile, $newConfig);

$this->info('Essential Pest configuration added successfully.');

return Command::SUCCESS;
}

/**
* Get the stub file for the generator.
*/
private function getStub(): string
{
return $this->resolveStubPath('/stubs/pest.stub');
}

/**
* Resolve the fully-qualified path to the stub.
*/
private function resolveStubPath(string $stub): string
{
$basePath = base_path(ltrim($stub, '/'));

return File::exists($basePath)
? $basePath
: __DIR__.'/../../'.$stub;
}
}
2 changes: 2 additions & 0 deletions src/EssentialsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace NunoMaduro\Essentials;

use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use NunoMaduro\Essentials\Commands\EssentialsPestCommand;
use NunoMaduro\Essentials\Commands\EssentialsPintCommand;
use NunoMaduro\Essentials\Commands\MakeActionCommand;
use NunoMaduro\Essentials\Contracts\Configurable;
Expand Down Expand Up @@ -44,6 +45,7 @@ public function boot(): void

if ($this->app->runningInConsole()) {
$this->commands([
EssentialsPestCommand::class,
EssentialsPintCommand::class,
MakeActionCommand::class,
]);
Expand Down
28 changes: 28 additions & 0 deletions stubs/pest.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
|--------------------------------------------------------------------------
| Essentials
|--------------------------------------------------------------------------
|
| This configuration enhances your test suite with reliability features that
| prevent common testing issues. By controlling randomness, time, and external
| connections, these settings ensure your tests run consistently and predictably
| across environments, preventing flaky tests and improving debugging efficiency.
|
| These defaults make tests faster, more isolated, and more deterministic without
| requiring manual configuration for each test case in your application.
|
*/

use Illuminate\Support\Sleep;

pest()->extend(Tests\TestCase::class)
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
->beforeEach(function (): void {
Str::createRandomStringsNormally();
Str::createUuidsNormally();
Http::preventStrayRequests();
Sleep::fake();

$this->freezeTime();
})
->in('Feature', 'Unit');
77 changes: 77 additions & 0 deletions tests/Commands/EssentialsPestCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

use Illuminate\Support\Facades\File;

$cleanup = function (): void {
$pestFile = base_path('tests/Pest.php');
if (File::exists($pestFile)) {
File::delete($pestFile);
}

$stubsPath = base_path('stubs');
if (File::exists($stubsPath)) {
File::deleteDirectory($stubsPath);
}
};

beforeEach(fn () => $cleanup());
afterEach(fn () => $cleanup());

function createBasePestFile(): void
{
$pestFile = base_path('tests/Pest.php');
$stubContent = File::get(__DIR__.'/../stubs/pest-base.stub');
File::put($pestFile, $stubContent);
}

it('adds essential pest configuration when Pest.php exists', function (): void {
createBasePestFile();
$pestFile = base_path('tests/Pest.php');

$this->artisan('essentials:pest')
->assertSuccessful();

$content = File::get($pestFile);
expect($content)
->toContain('// added with Essentials Script')
->toContain('Http::preventStrayRequests()')
->toContain('Sleep::fake()')
->toContain('->in(\'Feature\', \'Unit\')');
});

it('skips adding configuration if already present', function (): void {
$pestFile = base_path('tests/Pest.php');
File::put($pestFile, "<?php\n\n// added with Essentials Script\n// some content");

$this->artisan('essentials:pest')
->assertFailed()
->expectsOutput('Essential Pest configuration already added.');
});

it('shows a friendly error when Pest.php does not exist', function (): void {
$this->artisan('essentials:pest')
->assertFailed()
->expectsOutput('🐞 Show some love to Pest! For Nuno\'s sake, install it first! 🚀');
});

it('uses published stub when available', function (): void {
createBasePestFile();
$pestFile = base_path('tests/Pest.php');

$this->artisan('vendor:publish', ['--tag' => 'essentials-stubs'])
->assertSuccessful();

$publishedStubPath = base_path('stubs/pest.stub');
File::put($publishedStubPath, "// This is a custom stub\npest()->extend(Tests\TestCase::class)");

$this->artisan('essentials:pest')
->assertSuccessful();

$content = File::get($pestFile);
expect($content)
->toContain('// added with Essentials Script')
->toContain('// This is a custom stub')
->not->toContain('Sleep::fake()');
});
47 changes: 47 additions & 0 deletions tests/stubs/pest-base.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "pest()" function to bind a different classes or traits.
|
*/

pest()->extend(Tests\TestCase::class)
// ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
->in('Feature');

/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/

expect()->extend('toBeOne', function () {
return $this->toBe(1);
});

/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/

function something()
{
// ..
}