You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

问题根源与解决方案

核心问题

  1. 构造函数手动实例化依赖,绕过Mock:Job的构造函数中直接用app()->make()创建FixtureReminderEmail实例,此时测试中的Mock还未生效,导致实际运行时使用的是真实类实例而非Mock对象。
  2. 测试逻辑错误:
    • 初始测试用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 14:20:22