使用PHPUnit和Mockery进行Laravel测试 - 在控制器测试中设置依赖项

7

终于让我那个愚蠢简单的测试通过了,但我有一种感觉,好像做得不对。

我有一个SessionsController,负责显示登录页面和登录用户。

我决定不使用facade,这样我就不必扩展Laravel的TestCase并在单元测试中产生性能损失。因此,我通过控制器注入了所有依赖项,像这样 -

SessionsController - 构造函数

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

我已经声明了必要的变量并使用了命名空间,这里不必介绍。

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

SessionsController - Create

public function create()
{
    if ($this->auth->user()) return $this->redirect->to('/');

    return $this->view->make('sessions.login');
}

现在进行测试,我对此全新,所以请谅解。

SessionsControllerTest

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声明所有这些模拟依赖项。是否有一种方法可以在构造函数中声明这些模拟依赖项,并通过它们的变量进行调用来进行模拟?
2个回答

11
您可以使用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');
}

然而,如果你在测试中模拟设置不同,那么这种方法就行不通了。针对这种情况,你可以使用一个辅助方法:
protected function getAuthMock($isLoggedIn = false)
{
    $authorizedUser = Mockery::mock('Illuminate\Auth\AuthManager');
    $authorizedUser->shouldReceive('user')->once()->andReturn($isLoggedIn);
}

当您需要进行身份验证模拟时,只需调用 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;
    }
}

谢谢@watcher!我很感激你详尽的回答。 - Mitch

1
是的,你可以使用“helper”。将模拟依赖项的创建移动到另一个函数中,然后在需要它们时调用该函数。请查看此演示文稿中的第52张幻灯片:https://speakerdeck.com/jcarouth/guiding-object-oriented-design-with-tests-1(当然可以查看整个演示文稿,但示例在第52张幻灯片上)
编辑:setUp方法更好,我原来想的是某些测试不需要,但我认为对于你描述的情况,在setUp中执行会更好。

谢谢你提供这个参考,@Jessica,我很感激。 - Mitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接