【问题标题】:Laravel partial mocked model not invoked未调用 Laravel 部分模拟模型
【发布时间】:2014-09-14 17:36:39
【问题描述】:

我正在使用 laravel (4.2) 框架来开发 Web 应用程序 (PHP 5.4.25)。我创建了一个使用 eloquent-repository 实现的存储库接口,我在 UserController 中使用该存储库:

#   app/controllers/UsersController.php

use Gas\Storage\User\UserRepositoryInterface as User;

class UsersController extends \BaseController {
    protected $user;

    public function __construct(User $user) {
        $this->user = $user;

    }

    public function store() {
        $input = Input::all();
        $validator = Validator::make(Input::all(), $this->user->getRoles());

        if ( $validator->passes() ) {
            $this->user->getUser()->username = Input::get('username');
            $this->user->getUser()->password = Hash::make(Input::get('password'));
            $this->user->getUser()->first_name = Input::get('first_name');
            $this->user->getUser()->last_name = Input::get('last_name');
            $this->user->getUser()->email = Input::get('email');
            $this->user->save();


            return false;
        } else {
            return true;
        }
    }
}

我的存储库实现:

namespace Gas\Storage\User;

#   app/lib/Gas/Storage/User/EloquentUserRepository.php

use User;

class EloquentUserRepository implements UserRepositoryInterface {

    public $_eloquentUser;

    public function __construct(User $user) {
        $this->_eloquentUser = $user;
    }

    public function all()
    {
        return User::all();
    }

    public function find($id)
    {
        return User::find($id);
    }

    public function create($input)
    {
        return User::create($input);
    }

    public function save()
    {
        $this->_eloquentUser->save();
    }

    public function getRoles()
    {
        return User::$rules;
    }

    public function getUser()
    {
        return $this->_eloquentUser;
    }
}

我还创建了一个 UsersControllerTest 来测试控制器,一切正常,用户已添加到数据库中。在我嘲笑了我的 UserRepositoryInterface 之后,因为我不需要测试数据库插入,但我只想测试控制器

class UsersControllerTest extends TestCase {
    private $mock;

    public function setUp() {
        parent::setUp();
    }

    public function tearDown() {
        Mockery::close();
    }

    public function mock($class) {
        $mock = Mockery::mock($class);

        $this->app->instance($class, $mock);

        return $mock;
    }

    public function testStore() {
        $this->mock = $this->mock('Gas\Storage\User\UserRepositoryInterface[save]');

        $this->mock
            ->shouldReceive('save')
            ->once();

        $data['username'] = 'xxxxxx';
        $data['first_name'] = 'xxxx';
        $data['last_name'] = 'xxxx';
        $data['email'] = 'prova@gmail.com';
        $data['password'] = 'password';
        $data['password_confirmation'] = 'password';

        $response = $this->call('POST', 'users', $data);

        var_dump($response->getContent());
    }
}

我的 ruote 文件:

Route::resource('users', 'UsersController');

当我运行测试时,我收到以下错误:

Mockery\Exception\InvalidCountException : Method save() from Mockery_0_Gas_Storage_User_UserRepositoryInterface should be called
 exactly 1 times but called 0 times.

为什么没有调用模拟方法save

怎么了?


编辑:没有部分模拟一切正常,现在的问题是:为什么部分模拟它不起作用?


谢谢

【问题讨论】:

  • 看起来你正在模拟的类的命名空间在“模拟”方法调用中与控制器上的“使用”语句中的命名空间不同。一个以“Gas”开头,另一个以“Way”开头。
  • @patricksayshi 这是我在写问题时犯的错误
  • 你试过没有[save] 部分吗?这似乎会使 IoC 容器感到困惑。即,如果您的控制器构造函数被类型提示期望“Gas\Storage\User\UserRepositoryInterface[save]”,它将返回正确的模拟。但是类型提示需要“Gas\Storage\User\UserRepositoryInterface”,因此 IoC 容器将返回您之前绑定到该接口的任何内容。您希望它覆盖原始的 IoC 绑定,但由于 '[save]' 存在,而且它是一个不同的字符串,我希望它只会创建一个新的绑定并且不会将模拟返回到测试。
  • @hakre OP 的代码中没有什么是不能模拟的。我每天都在 laravel 中对对象进行单元测试和模拟。你是对的,你不能模拟对模型的静态调用。但是OP没有这些。对外观的静态调用(Input::all()、Validator::make() 等)确实可以被模拟,这是 Laravel 设计的一部分。我仍然认为这里的问题是“[save]”字符串混淆了 IoC 容器。删除它应该可以解决这个问题,此时 philipbrown 的回答将变得非常重要。
  • @PapaSmurf 我想你是想回复我的。它不适用于部分模拟,因为在 IoC 容器上,您将 UserRepositoryInterface[save] 绑定到您创建的模拟。但是您的应用程序代码从不要求 IoC 容器提供UserRepositoryInterface[save] 的实例。您可以在控制器构造函数中看到它要求UserRepositoryInterface。我不确定您是否可以在这种情况下使用部分模拟。我很好奇 - 为什么要使用部分模拟?如果是我,我只会嘲笑全班,不再烦恼。

标签: php unit-testing laravel mockery partial-mocks


【解决方案1】:

回顾您的代码,您似乎应该能够通过将模拟函数更改为以下内容来使用部分模拟:

public function mock($class) {
    $mock = Mockery::mock($class);
    $ioc_binding = preg_replace('/\[.*\]/', '', $class);
    $this->app->instance($ioc_binding, $mock);

    return $mock;
}

【讨论】:

  • 我已经在我的代码中实现了你的模拟函数,但是当我运行测试时,我收到了以下错误:BadMethodCallException : Method Mockery_0_Gas_Storage_User_UserRepositoryInterface::getRoles() does not exist on this mock object。貌似部分mock不行,但是期望mock的对象的所有方法都实现了。
  • 好吧,至少您知道现在您的模拟已通过测试。 “似乎......它期望模拟对象的所有方法都被实现” - 这是完全正确的。您需要使用“shouldReceive()”来告诉模拟对将要调用的每个方法 执行什么操作。是的,这是一个严重的痛苦。如果您在设计类时考虑到这一点,那么从长远来看,您将编写更好/更简单的代码。例如,您的 repo 的 save() 函数可以接受 all 输入数据并将其传递给模型。那么你的 UserController 就不需要知道模型了
  • 我希望单独的业务逻辑和 dao(数据访问对象),因此我将输入验证放在控制器中。
【解决方案2】:

您告诉 mock 期待 save() 方法,但 save() 位于存储库内的 Eloquent 模型上,而不是您正在模拟的存储库。

您的代码目前正在泄露存储库的实现细节。

而不是调用:

$this->user->getUser()->username = Input::get('username');

您需要将User 的实例传递到存储库中:

$this->user->add(User::create(Input::all());

或者您将Input 的数组传递到存储库并允许存储库在内部创建一个新的User 实例:

$this->user->add(Input::all());

然后,您将在测试中模拟 add() 方法:

$this->mock->shouldReceive('add')->once();

关于 Laravel 不适合模拟或单元测试的 cmets 是错误的。

【讨论】:

  • 我已经尝试按照你说的做,而是打电话给save(),我打电话给create(Input::all()),但结果是一样的,我的嘲笑电话被忽略了。我已将我的存储库实现添加到我的问题中。
  • 没有部分模拟 $this->mock = $this->mock('Gas\Storage\User\UserRepositoryInterface'); 工作正常,但使用 $this->mock = $this->mock('Gas\Storage\User\UserRepositoryInterface[save]'); 它不起作用。为什么?
猜你喜欢
  • 2014-06-02
  • 2023-03-22
  • 2015-08-08
  • 2020-10-30
  • 2020-02-29
  • 2015-03-12
  • 2014-11-05
  • 2018-05-25
  • 2011-02-15
相关资源
最近更新 更多