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