Laravel 9队列任务调用外部类的测试问题求助
Laravel 9 Job测试问题:验证FixtureReminderEmail的handle方法是否被调用
问题描述
我在测试Laravel 9中的SendFixtureReminderEmailJob是否调用了FixtureReminderEmail类的handle方法,已经模拟了该类,但测试始终失败,提示预期方法未被调用。尝试过调试、使用dispatchSync、直接调用job的handle方法等多种测试方式,都没能解决问题,现附上相关代码,寻求正确的测试方案。
Job代码
class SendFixtureReminderEmailJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * The number of times the job may be attempted. * * @var int */ public int $tries = 3; /** * The number of seconds the job can run before timing out. * * @var int */ public int $timeout = 240; private League $league; private Fixture $fixture; private FixtureReminderEmail $fixtureReminderEmail; /** * Create a new job instance. */ public function __construct(League $league) { $this->league = $league; $this->fixtureReminderEmail = app()->make(FixtureReminderEmail::class, ['league'=>$this->league]); } /** * Get the tags that should be assigned to the job. * * @return array */ public function tags() { return ['fixtureReminderEmail']; } /** * @return void * @throws Exception */ public function handle(): void { $counter = $this->fixtureReminderEmail->handle(); Log::info($counter . " Fixture " . Str::plural('reminder', $counter) . "sent"); } }
测试代码
初始测试代码
class SendFixtureReminderJobTest extends TestCase { use RefreshDatabase; use WithFaker; private $adminUser; protected function setUp(): void { parent::setUp(); } /** * @test * @covers SendEclecticUpdateEmailJob::handle * @description: */ public function testHandle() { Queue::fake(); $league = League::factory()->matchplay()->create(); $this->mock(FixtureReminderEmail::class, function($mock) use ($league) { $mock->shouldReceive('handle') ->with($league) ->once() ->andReturn(6); }); $this->withoutExceptionHandling(); $job = new SendFixtureReminderEmailJob($league); $job->dispatch($league); } }
测试变体1(使用dispatchSync)
public function testHandle() { // Queue::fake(); $league = League::factory()->matchplay()->create(); $this->mock(FixtureReminderEmail::class, function($mock) use ($league) { $mock->shouldReceive('handle') ->with($league) ->once() ->andReturn(6); }); $this->withoutExceptionHandling(); $job = new SendFixtureReminderEmailJob($league); $job->dispatchSync($league); // Queue::assertPushed(SendFixtureReminderEmailJob::class, 1); }
测试变体2(直接调用handle)
public function testHandle() { // Queue::fake(); $league = League::factory()->matchplay()->create(); $this->mock(FixtureReminderEmail::class, function($mock) use ($league) { $mock->shouldReceive('handle') ->with($league) ->once() ->andReturn(6); }); $this->withoutExceptionHandling(); $job = new SendFixtureReminderEmailJob($league); $job->handle($league); // Queue::assertPushed(SendFixtureReminderEmailJob::class, 1); }
问题根源与解决方案
核心问题
- 构造函数手动实例化依赖,绕过Mock:Job的构造函数中直接用
app()->make()创建FixtureReminderEmail实例,此时测试中的Mock还未生效,导致实际运行时使用的是真实类实例而非Mock对象。 - 测试逻辑错误:
- 初始测试用
Queue::fake()会阻止Job实际执行,handle方法不会被触发,Mock自然无法检测到调用。 - 变体2中给
handle()传参是错误的,Job的handle方法没有参数,传参会导致方法签名不匹配。
- 初始测试用
修正后的Job代码
通过构造函数注入依赖,让Laravel服务容器负责解析FixtureReminderEmail,这样测试时的Mock才能被正确注入:
class SendFixtureReminderEmailJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $tries = 3; public int $timeout = 240; private League $league; private FixtureReminderEmail $fixtureReminderEmail; // 构造函数注入依赖,替代手动解析 public function __construct(League $league, FixtureReminderEmail $fixtureReminderEmail) { $this->league = $league; $this->fixtureReminderEmail = $fixtureReminderEmail; } public function tags() { return ['fixtureReminderEmail']; } public function handle(): void { $counter = $this->fixtureReminderEmail->handle(); Log::info($counter . " Fixture " . Str::plural('reminder', $counter) . " sent"); } }
修正后的测试代码
class SendFixtureReminderJobTest extends TestCase { use RefreshDatabase; use WithFaker; protected function setUp(): void { parent::setUp(); } /** * @test * @covers SendFixtureReminderEmailJob::handle */ public function test_handle_calls_fixture_reminder_email_handle_method() { $league = League::factory()->matchplay()->create(); // 提前Mock依赖,定义预期行为 $this->mock(FixtureReminderEmail::class, function ($mock) { $mock->shouldReceive('handle') ->once() ->andReturn(6); }); // 通过服务容器创建Job实例,自动注入Mock $job = app(SendFixtureReminderEmailJob::class, ['league' => $league]); // 直接调用handle方法执行测试逻辑 $job->handle(); } }
另一种测试方式(使用dispatchSync)
如果需要测试Job的调度逻辑,可使用dispatchSync,此时Laravel会自动解析并注入Mock:
public function test_handle_calls_fixture_reminder_email_handle_method() { $league = League::factory()->matchplay()->create(); $this->mock(FixtureReminderEmail::class, function ($mock) { $mock->shouldReceive('handle') ->once() ->andReturn(6); }); // 直接调度Job,Laravel负责解析依赖并执行 SendFixtureReminderEmailJob::dispatchSync($league); }
内容的提问来源于stack exchange,提问作者Ray
相关产品推荐
相关产品推荐

