Environment
Description
I'm trying to run a command every 1 minute using Laravel Task Scheduling.
Attempt
I've added this line to my cron tab file
* * * * * php artisan schedule:run >> /dev/null 2>&1
Here is my /app/Console/Kernel.php
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
\App\Console\Commands\Inspire::class,
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('inspire')->hourly();
$schedule->command('echo "Happy New Year!" ')->everyMinute(); //<---- ADD HERE }
}
I've added this line $schedule->command('echo "Happy New Year!" ')->everyMinute();
Question
How do I test this ?
How do I trigger my echo to display ?
How do I know if what I did is not wrong ?
I'm opening to any suggestions at this moment.
Any hints / suggestions / helps on this be will be much appreciated !
command()
runs an artisan command. What you're trying to achieve - issuing a command to the OS - is done by exec('echo "Happy New Year!"')
Testing depends on what you want to test:
- Whether the scheduler (every minute) is working?
In this case, you don't have to. It is tested in the original framework code.
- Whether the command succeeds?
Well, you can manually run php artisan schedule:run
and see the output.
The scheduler does not produce any output on default (>> /dev/null 2>&1
). You can, however, redirect the output of the runned scripts to any file by chaining writeOutputTo()
or appendOutputTo()
(https://laravel.com/docs/5.1/scheduling#task-output).
For more complex logic, write a console command instead (https://laravel.com/docs/5.1/artisan#writing-commands) and use command()
- this way you can write nice, testable code.
If you want to unit test the scheduling of events you can use this example. It is based on the default inspire command:
public function testIsAvailableInTheScheduler()
{
/** @var \Illuminate\Console\Scheduling\Schedule $schedule */
$schedule = app()->make(\Illuminate\Console\Scheduling\Schedule::class);
$events = collect($schedule->events())->filter(function (\Illuminate\Console\Scheduling\Event $event) {
return stripos($event->command, 'YourCommandHere');
});
if ($events->count() == 0) {
$this->fail('No events found');
}
$events->each(function (\Illuminate\Console\Scheduling\Event $event) {
// This example is for hourly commands.
$this->assertEquals('0 * * * * *', $event->expression);
});
}
Building on Michiel's answer, I've used the methods contained in Illuminate\Console\Scheduling\Event
to test if the event is due to run for a given date.
I've mocked the current date using Carbon::setTestNow()
so that any date based logic in the when()
and skip()
filters will behave as expected.
use Tests\TestCase;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Console\Scheduling\Event;
use Cron\CronExpression;
use Carbon\Carbon;
class ScheduleTest extends TestCase {
public function testCompanyFeedbackSchedule()
{
$event = $this->getCommandEvent('your-command-signature');
$test_date = Carbon::now()->startOfDay()->addHours(8);
for ($i=0; $i < 356; $i++) {
$test_date->addDay();
Carbon::setTestNow($test_date);
// Run the when() & skip() filters
$filters_pass = $event->filtersPass($this->app);
// Test that the Cron expression passes
$date_passes = $this->isEventDue($event);
$will_run = $filters_pass && $date_passes;
// Should only run on first friday of month
if ($test_date->format('l') === 'Friday' && $test_date->weekOfMonth === 1) {
$this->assertTrue($will_run, 'Task should run on '. $test_date->toDateTimeString());
} else {
$this->assertFalse($will_run, 'Task should not run on '. $test_date->toDateTimeString());
}
}
}
/**
* Get the event matching the given command signature from the scheduler
*
* @param string $command_signature
*
* @return Illuminate\Console\Scheduling\Event
*/
private function getCommandEvent($command_signature)
{
$schedule = app()->make(Schedule::class);
$event = collect($schedule->events())->first(function (Event $event) use ($command_signature) {
return stripos($event->command, $command_signature);
});
if (!$event) {
$this->fail('Event for '. $command_signature .' not found');
}
return $event;
}
/**
* Determine if the Cron expression passes.
*
* Copied from the protected method Illuminate\Console\Scheduling\Event@isEventDue
*
* @return bool
*/
private function isEventDue(Event $event)
{
$date = Carbon::now();
if ($event->timezone) {
$date->setTimezone($event->timezone);
}
return CronExpression::factory($event->expression)->isDue($date->toDateTimeString());
}
}