使用 Mockery Eloquent 模型模拟 Laravel

Posted

技术标签:

【中文标题】使用 Mockery Eloquent 模型模拟 Laravel【英文标题】:Laravel mock with Mockery Eloquent models 【发布时间】:2014-11-05 02:42:57 【问题描述】:

我正在开发一个带有 laravel(4.2) 框架的 PHP (5.4.25) 应用程序。我想用 Mockery 测试我的 UserController,所以我用这种方式安装了我的 UserController:

class UsersController extends \BaseController 
    protected $user;

    public function __construct(User $user) 
        $this->user = $user;
        $this->beforeFilter('csrf', array('on'=>'post'));
    

    public function store() 
        $validator = Validator::make(Input::all(), User::$rules);

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


            return true;
         else 
            return false;
        
    

我想要模拟 Eloquent User 模型,所以我开发了我的 UsersControllerTest:

class UsersControllerTest extends TestCase 

    private $mock;

    public function __construct() 

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

        $this->createApplication();                                                     
    

    public function tearDown() 
        parent::tearDown();

        Mockery::close();
    

    public function testStore() 
        $this->mock = Mockery::mock('Eloquent','User[save]');                                           
        $this->mock
            ->shouldReceive('save')
            ->once()
            ->andReturn('true');                                                        
        $this->app->instance('User', $this->mock);                                      

        $data['username'] = 'qwerety';
        $data['first_name'] = 'asd';
        $data['last_name'] = 'asd123';
        $data['email'] = 'test@gmail.com';
        $data['password'] = 'password';
        $data['password_confirmation'] = 'password';

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

        var_dump($response->getContent());
    

 

当我运行我的测试时,它会返回这个错误:

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

为什么?怎么了?

编辑: 我发现了问题 - 如果我不使用模拟对象一切正常,并且控制器在数据库中创建一个新用户,但是当我使用模拟时,Input:all() 方法返回空数组。

-- 谢谢

【问题讨论】:

【参考方案1】:

您测试这个的方式是,在控制器构造函数中传递一个真实 Eloquent 模型的实例,而不是模拟。如果你想测试门面(正如documentation 中明确说明的那样),你应该直接在门面上调用shouldReceive() 方法来模拟它。

但是,由于您在控制器的store() 方法中重新定义了$this->user 变量,因此不会调用它,除非您删除硬编码的实例化并使用注入的$user

编辑:我忽略了$this->app->instance('User', $this->mock); 行。因此,问题可能是由于在 store 方法中您直接获得了一个新的类实例,而不是通过 Laravel IoC 容器。而不是你的 store 方法中的$this->user = new User;,你应该通过App::make('User');得到它

【讨论】:

在同样的错误中也删除了 store 方法中的新类实例。【参考方案2】:

当我开始测试时,我遇到了同样的问题..... 问题是,在您的 userscontroller 存储方法中,您实际上是将用户保存到数据库并基于您的代码它可能工作得很好,但您会惊讶于它实际上没有通过测试。 现在这样想,你模拟用户,你告诉你的测试,当我调用用户模型时,请给我我的用户的模拟版本,就像你在下面的代码行中所做的那样

$this->app->instance('User', $this->mock);

现在的问题是,您还需要从 User 的模拟版本调用 save() 方法的模拟版本,如下所示:

$this->mock->save();

考虑以下示例:

public function testStore()

    $input = ['name', 'Canaan'];

    $this->mock
        ->shouldReceive('create')
        ->once()->with($input);

    $this->app->instance('User', $this->mock);
    $this->mock->create($input);

    $this->call('POST', 'users', $input);


希望对你有帮助。

【讨论】:

如果我不使用模拟对象,一切正常,控制器在数据库中创建一个新用户,但是当我使用模拟时,Input:all() 方法返回空数组。 在测试类上再次调用$this->mock->create($input) 是否会破坏逻辑。对我来说,我觉得这是在欺骗测试并使其通过控制器测试,这意味着无论控制器(被测试的控制器)是否调用$this->user->save();,测试都将通过【参考方案3】:

问题出在$this->createApplication();

我已经评论了那行,Input::all() 可以很好地处理所有输入参数!

【讨论】:

以上是关于使用 Mockery Eloquent 模型模拟 Laravel的主要内容,如果未能解决你的问题,请参考以下文章

使用 Mockery Eloquent 模型模拟 Laravel

在 Laravel(流明)上使用 Mockery 模拟 Eloquent 模型不起作用

模拟静态 Eloquent 模型方法,包括 find()

使用 find() 模拟 Eloquent 模型

如何模拟 Laravel Eloquent 模型的静态方法?

在 Mockery 中测试链式方法调用