我正在为 Laravel 项目编写单元测试。
我想在下面的类中测试公共方法 testMethod()
class Foo extends Model
{
public static function staticMethod($arg)
{
return 'from staticMethod';
}
public function testMethod($arg)
{
$result = self::staticMethod($arg);
return $result;
}
}
这是我的测试代码:
1.
$mock = Mockery::mock('overload:'.Foo::class);
$mock->shouldReceive('staticMethod')
->once()
->with('test argument')
->andReturn('mocked return value');
$foo = new Foo();
$result = $foo->testMethod('test argument');
上面返回的结果是:
testMethod() does not exist on this mock object
.
2.
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('staticMethod')
->once()
->with('test argument')
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$foo = new Foo();
$result = $foo->testMethod('test argument');
然而,在这种情况下,
staticMethod()
没有被嘲笑。
编辑:
听从@Charlie的建议,我重写了测试代码如下
$mock = \Mockery::mock(Foo::class);
$mock->shouldReceive('staticMethod')
->once()
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$mockedFoo = app(Foo::class);
$result = $mockedFoo->testMethod('test argument');
这里有一个问题。
// return 'mocked return value'
$mockedFoo->staticMethod('test');
// return value of original method.
Foo::staticMethod('test');
在被测代码中,我用
Foo::staticMethod()
调用它,而不是实例。
我仍在尝试这段代码,但是否可以在不重载的情况下模拟静态方法?
这个测试返回;
Mockery\Exception\BadMethodCallException:收到 Mockery_2_App_Models_Foo::testMethod(),但未指定预期
这个错误意味着模拟识别
testMethod()
(我很高兴!)。
但是我想让
testMethod()
按照原来的代码工作,所以我不想给mock设置一个返回值。
所以,我用了
makePartial()
.
$mock = \Mockery::mock('overload:'.Foo::class)->makePartial();
然后,从现在开始,跑测试的时候就不用
staticMethod()
mock了,而是用原来的staticMethod()
...
有人有什么想法吗?
你的两个测试包含同样的问题,你没有使用 Laravel 应用程序来获取实例化类,当你使用
new Foo()
时,它实际上实例化了一个真正的 Foo::class 而不是模拟类。
一个未经测试的例子:
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('staticMethod')->once()->with('test argument')->andReturn('mocked return value');
// This line will replace the origin Foo::class with the mocked Foo::class.
$this->app->instance(Foo::class, $mock);
// $foo = new Foo(); Instantiate like this won't use the mocked Foo::class.
// Use "app(Foo::class)" to instantiate the mocked class.
$mockedFoo = $this->app(Foo::class);
$result = $mockedFoo->testMethod('test argument');`
你快到了,新代码的问题是你没有要求部分模拟,所以无论你没有模拟什么,都会返回一个错误(未定义)。但是你还需要超载。
我将分享一个解决方案,快速修复您的代码:
$mock = \Mockery::mock('overload:'.Foo::class)->makePartial();
$mock->shouldReceive('staticMethod')
->once()
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$mockedFoo = app(Foo::class); // This part makes no sense on the test, as you already have the $mock, so directly call the mock
$result = $mockedFoo->testMethod('test argument');
但是你原来的代码需要改一下,不是
self::staticMethod($arg)
,一定是static::staticMethod($arg)
所以当Mockery
重载Foo
类来模拟静态调用,static
指的是后期绑定( Mockery
正在做类似 MockeryClass1 extends Foo
的事情,所以如果您有 self
,它将始终指代 Foo
,所以没有模拟,但 static
在这种情况下将始终指代 MockeryClass1
,因此静态模拟方法将起作用)。
所以,在代码方面,Mockery 做了这样的事情:
// When you do this
$mock = \Mockery::mock('overload:'.Foo::class)->makePartial();
// Mockery does something like
class Mocker_class_fake_1 extends Foo
{
// ...
public function __call()
{
// ...
}
public function __callStatic()
{
// ...
}
}
/**
* So when you do $mock->shouldReceive('xyz') and then $mock->xyz()
* Mockery will use PHP __call ($this->method) magic method to resolve
* what to do.
*
* __callStatic does the same but for static calls. (Class::method)
*
* Having makePartial() does the same, only that will use original code
* instead of erroring that there is no definition for that method call.
*/
现在,如果你的代码是:
public function testMethod($arg)
{
return self::staticMethod($arg);
}
当使用那个
Mocker_class_fake_1
示例类时,self
总是解析为Foo
,但是static
解析为扩展和调用该方法的那个,在这种情况下Mocker_class_fake_1::staticMethod($arg)
然后__callStatic
将能够解析staticMethod
因为你写了shouldReceive('staticMethod')
等
但是你需要三样东西:
overload:
.Foo::类makePartial()
$this->app->instance(Foo::class, $mock);
和app(Foo::class);
(或resolve(Foo::class);
,app(...)
的确切别名)我的问题是为什么要扩展模型并对其进行测试,通常您不会混合使用它(当然可以,但这是一种不好的做法)。所以我会创建一个新的领域类,并将模型逻辑与领域逻辑分开,但我不确定为什么你会那样做,因为你只放了示例代码,而不是真正的代码。
希望这能澄清更多!
__call
和__callStatic
的更多信息。