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

Laravel Dusk测试中控制器Mock失效,仍进入真实控制器方法

问题

我正在学习Laravel测试并尝试使用Dusk。现有如下CoursesController控制器及对应路由,该路由被VueJS组件调用,我希望通过Dusk进行浏览器测试时Mock控制器的getCoursesCache方法响应。但测试时控制器方法内的断点总是被触发,尝试了两种Mock方式,甚至Mock注入的ApiServiceProvider也无效。预期真实控制器方法不会被执行,请问问题出在哪里?

控制器代码

class CoursesController extends Controller {

    private ApiServiceProvider $api;

    public function __construct(ApiServiceProvider $apiServiceProvider) {
        $this->api = $apiServiceProvider;
    }
    public function getCoursesCache(array $cIds = []) : array {
// Breakpoint here - always gets hit when running tests
        if (empty($cIds)) {
            $cIds = Request::capture()->query('cIds');
            $cIds = explode(',', $cIds);
        }

        return $this->api->getCoursesCache($cIds);
    }
}

路由代码

Route::get('/api/v1/courses/cache', 'App\Http\Controllers\Api\CoursesController@getCoursesCache')->name('courses.cache');

尝试的Mock代码1

$courseController = $this->mock(CoursesController::class)->makePartial();
$item = new CourseCacheItem();
$item->name = $course->name;
$courseController->shouldReceive('getCoursesCache')
  ->with([$course->getKey()])
  ->andReturn([$item]);
$this->browse(function (Browser $browser) use ($course) {
  $browser->visit('/')
    ->waitFor('.course-card-container--data-loaded', 10)
    ->screenshot('filename')
    ->assertSee($course->name);
});

尝试的Mock代码2

$cc = $this->createMock(CoursesController::class);
$item = new CourseCacheItem();
$item->name = $course->name;
$cc->expects($this->once())->method('getCoursesCache')->with([$course->getKey()])->willReturn([$item]);

分析与解决

核心问题:Dusk的运行机制导致Mock失效

Dusk是浏览器端测试,它会发起真实的HTTP请求到你的应用,和单元测试的运行环境完全隔离:

  • 你在测试代码里创建的Mock对象,只存在于测试进程中
  • 处理浏览器请求的是另一个独立的应用进程,根本看不到你创建的Mock,所以还是会实例化真实的CoursesController并执行方法

正确的Mock方式

你需要针对应用容器绑定Mock,让应用进程在解析CoursesController或其依赖时拿到Mock实例,而不是在测试进程里创建Mock后不管:

方案1:Mock控制器的依赖ApiServiceProvider

既然控制器的getCoursesCache最终调用的是$this->api->getCoursesCache,直接Mock这个依赖更简单,而且能避免控制器逻辑被执行:

$this->mock(ApiServiceProvider::class, function ($mock) use ($course) {
    $item = new CourseCacheItem();
    $item->name = $course->name;
    $mock->shouldReceive('getCoursesCache')
        ->with([$course->getKey()])
        ->andReturn([$item]);
});

$this->browse(function (Browser $browser) use ($course) {
    $browser->visit('/')
        ->waitFor('.course-card-container--data-loaded', 10)
        ->assertSee($course->name);
});

通过$this->mock()方法,Laravel会自动把Mock实例绑定到服务容器中,应用进程在解析控制器依赖时会拿到这个Mock,从而跳过真实的API调用。

方案2:绑定Mock控制器到容器

如果你一定要Mock控制器本身,需要把Mock实例绑定到容器,替换掉真实的控制器:

$mockController = $this->mock(CoursesController::class);
$item = new CourseCacheItem();
$item->name = $course->name;
$mockController->shouldReceive('getCoursesCache')
    ->with([$course->getKey()])
    ->andReturn([$item]);

// 把Mock绑定到容器,让应用解析时用这个实例
$this->app->instance(CoursesController::class, $mockController);

$this->browse(function (Browser $browser) use ($course) {
    $browser->visit('/')
        ->waitFor('.course-card-container--data-loaded', 10)
        ->assertSee($course->name);
});

注意:这种方式需要确保控制器是通过容器解析的(Laravel默认路由解析控制器时会用容器,所以没问题)。

为什么之前的Mock无效?

  • 你创建的Mock只是测试进程中的一个对象,没有告诉Laravel的服务容器在解析控制器时使用它
  • Dusk的浏览器请求会触发新的应用实例,完全独立于测试代码的上下文,所以测试代码里的Mock对它毫无作用

内容的提问来源于stack exchange,提问作者Chris Rockwell

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:25:50