Testing
Setup
The scheduler package includes mocked WordPress functions in tests/bootstrap.php. These mocks cover:
add_action()/add_filter()/do_action()/apply_filters()get_transient()/set_transient()/delete_transient()wp_next_scheduled()/wp_schedule_event()/wp_unschedule_event()current_time()/is_multisite()/wp_doing_cron()
Running Tests
# Run all tests
composer test
# Run with verbose output
vendor/bin/phpunit --verbose
# Run a specific test class
vendor/bin/phpunit tests/Unit/ScheduleTest.php
# Run a specific test method
vendor/bin/phpunit --filter testCallReturnsScheduledTask
Testing Schedule Registration
use PHPUnit\Framework\TestCase;
use WPZylos\Framework\Scheduler\Schedule;
use WPZylos\Framework\Scheduler\ScheduledTask;
class MySchedulerTest extends TestCase
{
public function testTasksAreRegistered(): void
{
$schedule = new Schedule();
$schedule->call(function () {})->daily()->name('cleanup');
$schedule->call(function () {})->hourly()->name('sync');
$this->assertCount(2, $schedule->getTasks());
}
public function testCallbackTaskExecutes(): void
{
$schedule = new Schedule();
$executed = false;
$schedule->call(function () use (&$executed) {
$executed = true;
})->everyMinute()->name('test-task');
$schedule->run();
$this->assertTrue($executed);
}
}
Testing Frequency Configuration
public function testDailyAtSetsCorrectTime(): void
{
$task = new ScheduledTask(function () {}, 'callback');
$task->dailyAt('14:30');
$this->assertSame(86400, $task->getIntervalSeconds());
}
public function testWeeklyOnSetsCorrectDayAndTime(): void
{
$task = new ScheduledTask(function () {}, 'callback');
$task->weeklyOn(3, '10:00');
$this->assertSame(604800, $task->getIntervalSeconds());
}
Testing Constraints
public function testWhenConstraintBlocksExecution(): void
{
$task = new ScheduledTask(function () {}, 'callback');
$task->everyMinute()->when(fn() => false);
$this->assertFalse($task->isDue());
}
public function testSkipConstraintBlocksExecution(): void
{
$task = new ScheduledTask(function () {}, 'callback');
$task->everyMinute()->skip(fn() => true);
$this->assertFalse($task->isDue());
}
Testing Overlap Prevention
public function testOverlapPreventionBlocksExecution(): void
{
$GLOBALS['wp_transients'] = [];
$counter = 0;
$task = new ScheduledTask(function () use (&$counter) {
$counter++;
}, 'callback');
$task->everyMinute()->name('lock-test')->withoutOverlapping();
// Simulate existing lock
$lockKey = 'scheduler_lock_' . md5('lock-test');
set_transient($lockKey, time(), 3600);
$task->run();
$this->assertSame(0, $counter);
}
public function testOverlapLockIsReleasedAfterExecution(): void
{
$GLOBALS['wp_transients'] = [];
$task = new ScheduledTask(function () {
// Task completes
}, 'callback');
$task->everyMinute()->name('release-test')->withoutOverlapping();
$task->run();
$lockKey = 'scheduler_lock_' . md5('release-test');
$this->assertFalse(get_transient($lockKey));
}
Testing Error Handling
public function testFailedTaskFiresAction(): void
{
$GLOBALS['fired_actions'] = [];
$schedule = new Schedule();
$schedule->call(function () {
throw new \RuntimeException('Test failure');
})->everyMinute()->name('failing-task');
$schedule->run();
$this->assertArrayHasKey('wpzylos_scheduler_task_failed', $GLOBALS['fired_actions']);
}
Resetting State Between Tests
Always reset global state in setUp():
protected function setUp(): void
{
parent::setUp();
$GLOBALS['wp_transients'] = [];
$GLOBALS['fired_actions'] = [];
$GLOBALS['wp_scheduled_events'] = [];
}
Code Quality
# Static analysis
composer analyze
# Code style check
composer cs
# Fix code style
composer cs-fix
# Run everything
composer qa