Pest from scratch - Notes

After learning a few videos from this, this is maybe a version of a framework for testing because it's working on top of PHPUnit, and it looks so good and so much better than PHPUnit as the default, wait till you try the parallel.

https://pestphp.com/docs/installation

git clone <https://github.com/inertiajs/pingcrm.git>
composer require pestphp/pest-plugin-laravel --dev
npm install
npm run dev
//FROM THIS
<?php

namespace Tests\\Unit;

use PHPUnit\\Framework\\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function test_example()
    {
        $this->assertTrue(true);
    }
}

//TO THIS
test('example', function() {
    $this->assertTrue(true);
});

Hooks

beforeAll(fn() => var_dump('Before All'));
beforeEach(fn() => var_dump('Before Each'));
afterEach(fn() => var_dump('Before Each'));
afterAll(fn() => var_dump('After All'));
// SetUp()
protected function setUp(): void
    {
        parent::setUp();

        $this->user = User::factory()->create([
            'account_id' => Account::create(['name' => 'Acme Corporation'])->id,
            'first_name' => 'John',
            'last_name' => 'Doe',
            'email' => '[email protected]',
            'owner' => true,
        ]);

        $organization = $this->user->account->organizations()->create(['name' => 'Example Organization Inc.']);

        $this->user->account->contacts()->createMany([
            [
                'organization_id' => $organization->id,
                'first_name' => 'Martin',
                'last_name' => 'Abbott',
                'email' => '[email protected]',
                'phone' => '555-111-2222',
                'address' => '330 Glenda Shore',
                'city' => 'Murphyland',
                'region' => 'Tennessee',
                'country' => 'US',
                'postal_code' => '57851',
            ], [
                'organization_id' => $organization->id,
                'first_name' => 'Lynn',
                'last_name' => 'Kub',
                'email' => '[email protected]',
                'phone' => '555-333-4444',
                'address' => '199 Connelly Turnpike',
                'city' => 'Woodstock',
                'region' => 'Colorado',
                'country' => 'US',
                'postal_code' => '11623',
            ],
        ]);
    }

// TO

beforeEach(function() {
    $this->user = User::factory()->create([
        'account_id' => Account::create(['name' => 'Acme Corporation'])->id,
        'first_name' => 'John',
        'last_name' => 'Doe',
        'email' => '[email protected]',
        'owner' => true,
    ]);

    $organization = $this->user->account->organizations()->create(['name' => 'Example Organization Inc.']);

    $this->user->account->contacts()->createMany([
        [
            'organization_id' => $organization->id,
            'first_name' => 'Martin',
            'last_name' => 'Abbott',
            'email' => '[email protected]',
            'phone' => '555-111-2222',
            'address' => '330 Glenda Shore',
            'city' => 'Murphyland',
            'region' => 'Tennessee',
            'country' => 'US',
            'postal_code' => '57851',
        ], [
            'organization_id' => $organization->id,
            'first_name' => 'Lynn',
            'last_name' => 'Kub',
            'email' => '[email protected]',
            'phone' => '555-333-4444',
            'address' => '199 Connelly Turnpike',
            'city' => 'Woodstock',
            'region' => 'Colorado',
            'country' => 'US',
            'postal_code' => '11623',
        ],
    ]);
});

To use refresh database

use Illuminate\\Foundation\\Testing\\RefreshDatabase;

uses(RefreshDatabase::class);

//OR go to TestCase
use Illuminate\\Foundation\\Testing\\LazilyRefreshDatabase;

use LazilyRefreshDatabase;

public function test_can_view_contacts()
{
	...
}

test('can view contacts', function() {
	...
});

Pest faker

composer require pestphp/pest-plugin-faker --dev
use App\\Models\\Contact;
use function Pest\\Faker\\faker;

it('can store a contact', function() {
    login()->post('/contacts', [
        'first_name'=> faker()->firstName, 
        'last_name'=> faker()->lastName,
        'email'=> faker()->email,
        'phone'=> faker()->e164PhoneNumber,
        'address'=> 'Jatayu no 35',
        'city'=> 'TesterField',
        'region'=> 'Doe',
        'country'=> faker()->randomElement(['us', 'ca']),
        'postal_code'=>faker()->postcode,
    ])->assertRedirect('/contacts')->assertSessionHas('success','Contact created.');

    expect(Contact::latest()->first())
        ->first_name->toBeString()->not->toBeEmpty()
        ->last_name->toBeString()->not->toBeEmpty()
        ->email->toBeString()->toBeString()->toContain('@')
        ->phone->toBePhoneNumber()
        ->city->toBe('TesterField')
        ->country->toBeIn(['us', 'ca']);
});

Custom

// pest.php
function login($user = null)
{
    return test()->actingAs($user ?? User::factory()->create());
}

// create own
expect()->extend('toBePhoneNumber', function () {
    expect($this->value)->toBeString()->toStartWith('+');
    if (strlen($this->value) < 6) {
        throw new ExpectationFailedException('Phone numbers must be at least 6 characters');
    }

    if (! is_numeric(\\Str::of($this->value)->after('+')->remove([' ', '-'])->__toString())) {
        throw new ExpectationFailedException('Phone number must be numeric');
    }
});

Data sets

it('can store a contact', function(array $data) {
    login()->post('/contacts', [... [
        'first_name'=> faker()->firstName, 
        'last_name'=> faker()->lastName,
        'email'=> faker()->email,
        'phone'=> faker()->e164PhoneNumber,
        'address'=> 'Jatayu no 35',
        'city'=> 'TesterField',
        'region'=> 'Doe',
        'country'=> faker()->randomElement(['us', 'ca']),
        'postal_code'=>faker()->postcode,
    ], ...$data])->assertRedirect('/contacts')->assertSessionHas('success','Contact created.');

    expect(Contact::latest()->first())
        ->first_name->toBeString()->not->toBeEmpty()
        ->last_name->toBeString()->not->toBeEmpty()
        ->email->toBeString()->toBeString()->toContain('@')
        ->phone->toBePhoneNumber()
        ->city->toBe('TesterField')
        ->country->toBeIn(['us', 'ca']);
})->with([
    'generic' => [[]],
    'email with spaces' => [[ 'email' => '"madindo"@mailinator.com']],
    '.co.uk with sharon' => [['email' => '[email protected]', 'first_name' => 'info']],
    'postcode with 25 char' => [['postal_code' => str_repeat('a', 25)]],
]);


php artisan pest:dataset Emails
dataset('valid emails', function () {
    return [
        '[email protected]',
        '"luke downing"@downing.tech',
        '[email protected]',
        '[email protected]'
    ];
});‘˘
it('can store a contact', function($email) {
    ...
})->with('valid emails');

Groups

<?php
namespace App\\Rules;
use InvalidArgumentException;
use Illuminate\\Contracts\\Validation\\Rule;

class IsValidEmailAddress implements Rule
{
    public function passes($attribute, $value): bool 
    {
        if (! is_string($value)) {
            throw new InvalidArgumentException('The value must be a string');
        }
        return preg_match('/^\\S+@\\S+\\.\\S+$/', $value) > 0;
    }

    public function message() {

    }
}
php artisan test --group=laracasts

it('can validate an email', function() {
    $rule = new \\App\\Rules\\IsValidEmailAddress();
    expect($rule->passes('email', '[email protected]'))->toBeTrue();
})->group('laracasts');

php artisan test --exclude=laracasts

or

uses()->group('laracasts');
it('can validate an email', function() {
    $rule = new \\App\\Rules\\IsValidEmailAddress();
    expect($rule->passes('email', '[email protected]'))->toBeTrue();
});
it('throws an exception if the value is not a string', function() {
    $rule = new \\App\\Rules\\IsValidEmailAddress();
    expect($rule->passes('email', 1))->toBeTrue();
})->throws(InvalidArgumentException::class, 'The value must be a string')->group('current');
SKIP_TESTS=true php artisan test

it('throws an exception if the value is not a string', function() {
    $rule = new \\App\\Rules\\IsValidEmailAddress();
    expect($rule->passes('email', 1))->toBeTrue();
})->skip(getenv('SKIP_TESTS') ?? false, 'We no longer test exception')->throws(InvalidArgumentException::class, 'The value must be a string')->group('current');
it('throws an exception if the value is not a string', function() {
    $rule = new \\App\\Rules\\IsValidEmailAddress();
    expect($rule->passes('email', 1))->toBeTrue();
})
->skip(fn() => config('app.name') === 'foo', 'We no longer test exception')
->throws(InvalidArgumentException::class, 'The value must be a string')
->group('current');

Coverage and Parallel

XDEBUG_MODE=coverage php artisan test --coverage --min=80

https://pestphp.com/docs/plugins/parallel

Subscribe to You Live What You Learn

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe