Skip to content

Testing

Marko uses Pest PHP for testing and provides a marko/testing package with fakes for all major services.

Terminal window
composer require --dev marko/testing
Terminal window
# Run all tests
./vendor/bin/pest --parallel
# Run with coverage
./vendor/bin/pest --parallel --coverage --min=80
# Run a specific test file
./vendor/bin/pest tests/Feature/PostTest.php

Pest tests are expressive and concise:

tests/Feature/PostTest.php
<?php
declare(strict_types=1);
use App\Blog\Service\PostService;
use Marko\Testing\Fake\FakeEventDispatcher;
test('creates a post and dispatches event', function () {
$events = new FakeEventDispatcher();
$service = new PostService(events: $events);
$post = $service->createPost('Hello', 'World');
expect($post->title)->toBe('Hello')
->and($post->body)->toBe('World');
$events->assertDispatched(PostCreatedEvent::class);
});

The marko/testing package provides test doubles for all major interfaces:

FakeReplaces
FakeEventDispatcherEventDispatcherInterface
FakeMailerMailerInterface
FakeQueueQueueInterface
FakeSessionSessionInterface
FakeCookieJarCookieJarInterface
FakeLoggerLoggerInterface
FakeConfigRepositoryConfigRepositoryInterface
FakeAuthenticatableAuthenticatableInterface
FakeUserProviderUserProviderInterface

Each fake has assertXxx() methods for verifying behavior:

$mailer = new FakeMailer();
// ... code that sends mail
$mailer->assertSent(WelcomeEmail::class);
$mailer->assertSentCount(1);

Marko auto-loads custom expectations for cleaner assertions:

// Instead of: $events->assertDispatched(PostCreatedEvent::class)
expect($events)->toHaveDispatched(PostCreatedEvent::class);
// Instead of: $mailer->assertSent(WelcomeEmail::class)
expect($mailer)->toHaveSent(WelcomeEmail::class);
// Instead of: $queue->assertPushed(SendEmail::class)
expect($queue)->toHavePushed(SendEmail::class);
// Instead of: $logger->assertLogged('info', 'message')
expect($logger)->toHaveLogged('info', 'message');

Pass a flat dot-notation array:

$config = new FakeConfigRepository([
'auth.defaults.guard' => 'web',
'auth.guards.web.driver' => 'session',
]);
$service = new AuthService(config: $config);

Marko tests use present tense, concise names:

// Good
test('creates a post with valid data', function () { /* ... */ });
test('throws when title is empty', function () { /* ... */ });
// Avoid
test('it should be able to demonstrate creating a post', function () { /* ... */ });