在同一个类中模拟静态方法(Mockery,Laravel9)

问题描述 投票:0回答:2

我正在为 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()
...

有人有什么想法吗?

php laravel phpunit mockery
2个回答
0
投票

你的两个测试包含同样的问题,你没有使用 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');`

0
投票

你快到了,新代码的问题是你没有要求部分模拟,所以无论你没有模拟什么,都会返回一个错误(未定义)。但是你还需要超载。

我将分享一个解决方案,快速修复您的代码:

$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
的更多信息。

© www.soinside.com 2019 - 2024. All rights reserved.