如何在PHP测试中使用Mock对象?Laravel 5.5集成测试Mock失效问题
解决Laravel 5.5中集成测试无法Mock主类依赖的问题
首先,咱们先戳中核心问题:在你的集成测试里,Booking类压根没用到你Mock的CalEventCreator实例,反而从Laravel容器里捞了真实的服务出来用。这大概率是因为你没把Mock实例正确绑定到容器,或者Booking类的依赖注入方式让容器没法替你替换实例。
第一步:先确认Booking的依赖注入姿势
首先得保证你的Booking类是用构造函数注入依赖的(这是Laravel官方推荐的方式),别在类内部直接new CalEventCreator()——要是你自己手动实例化,容器根本插不上手。举个正确的例子:
// app/Booking.php class Booking { private $calEventCreator; // 构造函数注入依赖,让Laravel容器能管着这个实例 public function __construct(\App\CalEventCreator $calEventCreator) { $this->calEventCreator = $calEventCreator; } // 比如你的业务方法,会调用依赖组件的逻辑 public function completeBooking() { $calendarEvent = $this->calEventCreator->generateEvent(); // 其他业务操作... return $calendarEvent; } }
要是你的代码里是直接new CalEventCreator(),那先把这个改掉,换成构造注入,不然后面的操作都是白搭。
第二步:在集成测试里正确Mock依赖
Laravel测试里用Mockery来造Mock实例,然后把这个Mock绑定到容器里,这样当Booking被容器解析时,就会自动用Mock代替真实服务了。
完整测试代码示例:
// tests/Integration/BookingTest.php use Tests\TestCase; use App\Booking; use App\CalEventCreator; use Mockery; class BookingTest extends TestCase { // 每次测试完清理Mockery,避免泄漏 public function tearDown() { Mockery::close(); parent::tearDown(); } public function test_complete_booking_uses_mocked_calendar_service() { // 1. 造一个CalEventCreator的Mock实例 $mockCalService = Mockery::mock(CalEventCreator::class); // 2. 给Mock设定预期行为:比如调用generateEvent时返回预设值 $mockCalService->shouldReceive('generateEvent') ->once() // 预期被调用一次 ->andReturn(['event_id' => 'mock_123', 'status' => 'created']); // 3. 把Mock绑定到Laravel容器,替换掉真实的CalEventCreator $this->app->instance(CalEventCreator::class, $mockCalService); // 4. 从容器里拿Booking实例(或者通过控制器/服务调用,看你的集成场景) $booking = app(Booking::class); // 5. 执行测试方法并断言结果 $result = $booking->completeBooking(); $this->assertEquals(['event_id' => 'mock_123', 'status' => 'created'], $result); } }
常见坑点排查
- 直接new Booking而不用容器:要是你代码里是
new Booking()而不是app(Booking::class),那容器的绑定根本不生效。要么改成从容器拿实例,要么手动把Mock传入构造函数。 - Mock的方法/参数不匹配:要是你Mock的方法名拼错了,或者参数和实际调用的不一样,Mock会直接失效,转而调用真实服务。可以开启Mockery的严格模式排查:
Mockery::mock(CalEventCreator::class)->shouldDeferMissing(),或者看测试报错提示。 - 忘记清理Mockery:一定要在
tearDown里调用Mockery::close(),不然会导致后续测试出问题。 - 服务提供者绑定优先级:要是你在服务提供者里已经绑定了
CalEventCreator,不用担心——测试里的$this->app->instance()会覆盖之前的绑定,因为测试代码是在服务提供者加载之后执行的。
另一种灵活方式:直接注入Mock实例
要是你的测试场景不需要完全依赖容器,也可以直接把Mock实例塞进Booking的构造函数:
public function test_complete_booking_with_direct_mock() { $mockCalService = Mockery::mock(CalEventCreator::class); $mockCalService->shouldReceive('generateEvent') ->once() ->andReturn(['event_id' => 'mock_123']); // 手动把Mock传给Booking $booking = new Booking($mockCalService); $result = $booking->completeBooking(); $this->assertEquals(['event_id' => 'mock_123'], $result); }
这种方式更适合单元测试,但如果是集成测试(比如测试控制器调用Booking),还是得靠容器绑定来保证整个调用链都用Mock。
内容的提问来源于stack exchange,提问作者Ray
相关产品推荐
相关产品推荐

