使用PHPUnit和Mockery进行Laravel测试-设置对Controller测试的依赖关系


Laravel testing with PHPUnit and Mockery - Setting up dependencies on Controller test

在最终通过了我愚蠢的简单测试后,我觉得我做得不对。

我有一个SessionsController,它负责显示登录页面并将用户登录

我决定不使用facade,这样我就不必扩展Laravel的TestCase,也不必在单元测试中受到性能影响。因此,我已经通过控制器注入了所有的依赖项,比如so-

会话控制器-构造函数

public function __construct(UserRepositoryInterface $user, 
                            AuthManager $auth, 
                            Redirector $redirect,
                            Environment $view )
{
    $this->user = $user;
    $this->auth = $auth;
    $this->redirect = $redirect; 
    $this->view = $view;
}

我已经完成了必要的变量声明和名称空间的使用,我不打算在这里包括这些,因为这是不必要的。

create方法检测用户是否被授权,如果他们被授权,然后我将他们重定向到主页,否则他们将显示登录表单。

会话控制器-创建

public function create()
{
    if ($this->auth->user()) return $this->redirect->to('/');
    return $this->view->make('sessions.login');
}

现在测试,我是全新的,所以请耐心等待。

会话控制器测试

class SessionsControllerTest extends PHPUnit_Framework_TestCase {

    public function tearDown()
    {
        Mockery::close();
    }
    public function test_logged_in_user_cannot_see_login_page()
    {
        # Arrange (Create mocked versions of dependencies)
        $user = Mockery::mock('Glenn'Repositories'User'UserRepositoryInterface');
        $authorizedUser = Mockery::mock('Illuminate'Auth'AuthManager');
        $authorizedUser->shouldReceive('user')->once()->andReturn(true);
        $redirect = Mockery::mock('Illuminate'Routing'Redirector');
        $redirect->shouldReceive('to')->once()->andReturn('redirected to home');
        $view = Mockery::mock('Illuminate'View'Environment');

        # Act (Attempt to go to login page)
        $session = new SessionsController($user, $authorizedUser, $redirect, $view);
        $result = $session->create();
        # Assert (Return to home page) 
    }
}

这一切都通过了,但我不想为我在SessionsControllerTest中编写的每个测试声明所有这些模拟的依赖关系。有没有一种方法可以在构造函数中声明这些模拟依赖项?然后通过变量调用它们进行嘲讽?

您可以使用setUp方法来声明整个测试类的任何全局依赖关系。它类似于您当前使用的tearDown方法:

public function setUp()
{
   // This method will automatically be called prior to any of your test cases
   parent::setUp();
   $this->userMock = Mockery::mock('Glenn'Repositories'User'UserRepositoryInterface');
}

然而,如果您对mock的设置在不同的测试中有所不同,那么这将不起作用。对于这种情况,您可以使用辅助方法:

protected function getAuthMock($isLoggedIn = false)
{
    $authorizedUser = Mockery::mock('Illuminate'Auth'AuthManager');
    $authorizedUser->shouldReceive('user')->once()->andReturn($isLoggedIn);
}

然后,当您需要auth-mock时,您可以直接调用getAuthMock。这可以极大地简化您的测试。

但是

我认为你没有正确测试你的控制器。您不应该自己实例化控制器对象,而是应该使用Laravel的TestCase类中存在的call方法。请尝试查看Jeffrey Way的这篇关于测试Laravel控制器的文章。我认为你希望在测试中做更多的事情:

class SessionsControllerTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();
    }
    public function tearDown()
    {
        Mockery::close();
    }
    public function test_logged_in_user_cannot_see_login_page()
    {
        // This will bind any instances of the Auth manager during 
        // the next request to the mock object returned from the 
        // function below
        App::instance('Illuminate'Auth'Manager', $this->getAuthMock(true));
        // Act
        $this->call('/your/route/to/controller/method', 'GET');
        // Assert
        $this->assertRedirectedTo('/');
    }
    protected function getAuthMock($isLoggedIn)
    {
        $authMock = Mockery::mock('Illuminate'Auth'Manager');
        $authMock->shouldReceive('user')->once()->andReturn($isLoggedIn);
        return $authMock;
    }
}

是的,您可以使用"助手"。将模拟依赖关系的创建转移到另一个函数中,然后在需要时调用它。查看本演示文稿中的幻灯片52:https://speakerdeck.com/jcarouth/guiding-object-oriented-design-with-tests-1(我们来看看整件事,但这个例子在幻灯片52上)

编辑:设置方式甚至更好,我在想你在所有测试中都不需要的东西,但我认为你在设置中描述的方法要好得多。