使用Mockery模拟User静态方法时触发BadMethodCallException异常求助
排查Laravel中Mock静态模型方法触发BadMethodCallException的问题
问题原因分析
你遇到的BadMethodCallException本质是Laravel Eloquent的静态方法转发机制导致的:当你调用User::firstName()这类模型上不存在的静态方法时,Eloquent会自动创建一个Illuminate\Database\Query\Builder实例,并把方法调用转发给它。而你的Mock方式没有正确拦截这个静态调用,导致实际请求落到了查询构建器上——它自然没有firstName()方法,所以报错。
再看你的代码,问题出在Mock的逻辑上:你创建了一个User实例的Mock,然后用Test::double把firstName映射到这个实例,但这不是模拟静态方法的正确姿势。Test::double在这里并没有帮你拦截静态调用,所以User::firstName()还是走了Eloquent的默认转发逻辑。
解决办法
要正确模拟静态方法调用,你需要用Mockery的别名Mock(alias:前缀)来拦截整个User类的静态方法调用,然后指定它的返回值(如果需要返回实例,还要预先模拟实例的方法)。
方案1:直接模拟静态方法返回带实例方法的对象
// 创建别名Mock,拦截User类的静态方法调用 Mockery::mock('alias:User') ->shouldReceive('firstName') ->with('user-51e06817-c03c822b') ->andReturn((object) [ 'firstName' => function($id) { return 'Luis'; } ]); $userOP = User::firstName('user-51e06817-c03c822b'); $this->info($userOP->firstName('1'));
方案2:先模拟实例方法,再让静态方法返回该实例
如果firstName静态方法原本就应该返回User实例,这种方式更贴合真实逻辑:
// 先模拟User实例的firstName方法 $userInstance = Mockery::mock(User::class); $userInstance->shouldReceive('firstName') ->with('1') ->andReturn('Luis'); // 再模拟User类的静态firstName方法,返回上面的实例 Mockery::mock('alias:User') ->shouldReceive('firstName') ->with('user-51e06817-c03c822b') ->andReturn($userInstance); $userOP = User::firstName('user-51e06817-c03c822b'); $this->info($userOP->firstName('1'));
额外注意事项
- 如果你的测试是Laravel框架内的单元测试,建议使用
User::class替代字符串'User',避免命名空间问题。 - 测试完成后记得调用
Mockery::close()清理Mock实例,防止影响其他测试用例。
内容的提问来源于stack exchange,提问作者Luis Osuna
相关产品推荐
相关产品推荐

