使用 PHPUnit 和 Mockery 进行 Laravel 测试 - 设置对控制器测试的依赖

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23374247/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 09:26:43  来源:igfitidea点击:

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

phpunit-testinglaraveldependency-injectionlaravel-4

提问by Mitch

After finally getting my stupid simple test to pass, I have a feeling that I'm not doing it correctly.

在终于通过了我愚蠢的简单测试之后,我觉得我没有正确地做这件事。

I have a SessionsController, that is responsible for displaying a login page and logging a user in.

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

I have decided not to use facades so that I wouldn't have to extend Laravel's TestCase and take a performance hit on my unit tests. Therefore, I have injected all the dependencies through the controller, like so -

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

SessionsController- Constructor

SessionsController- 构造函数

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

I have done the necessary declaring of variables and using the namespaces, which I'm not going to include here as its unnecessary.

我已经完成了必要的变量声明并使用了命名空间,我不会在这里包含它,因为它是不必要的。

the create method detects if a user is authorized, if they are then I redirect them to the home page, otherwise they are displayed the login form.

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

SessionsController- Create

SessionsController- 创建

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

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

Now for the testing, I'm brand new to it, so bear with me.

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

SessionsControllerTest

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) 
    }
}

This all passes, but I don't want to have to declare all of these mocked dependencies for each test that I write in my SessionsControllerTest. Is there a way to declare these mocked dependencies once in say a constructor? and then call them by there variables for mocking?

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

回答by Jeff Lambert

You can use the setUpmethod to declare any dependencies that are global for the entire test class. It's similar to the tearDownmethod you're currently using:

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

However that won't work if your set up for the mock differs between tests. For this case you can use a helper method:

但是,如果您的模拟设置在测试之间有所不同,那么这将不起作用。对于这种情况,您可以使用辅助方法:

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

Then when you need the auth mock you can just call getAuthMock. This can greatly simplify your tests.

然后,当您需要 auth 模拟时,您只需调用getAuthMock. 这可以大大简化您的测试。

However

然而

I don't think you're testing your controller correctly. You shouldn't instantiate the controller object yourself, instead you should utilize the callmethod which exists in Laravel's TestCaseclass. Try checking out this articleabout testing Laravel Controllers by Jeffrey Way. I think you're looking to do something more along the lines of this in your test:

我认为您没有正确测试您的控制器。您不应该自己实例化控制器对象,而应该使用callLaravelTestCase类中存在的方法。尝试查看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;
    }
}

回答by Jessica

Yes, you can use a "helper". Move the creation of the mocked dependencies into another function, then call that when you need them. Check out slide 52 in this presentation: https://speakerdeck.com/jcarouth/guiding-object-oriented-design-with-tests-1(well check out the whole thing, but the example is on slide 52)

是的,您可以使用“帮手”。将模拟依赖项的创建移动到另一个函数中,然后在需要时调用它。查看本演示文稿中的幻灯片 52:https: //speakerdeck.com/jcarouth/guiding-object-orientation-design-with-tests-1(请查看整个内容,但示例在幻灯片 52 上)

Edit: The setUp way is even better, I was thinking for something you didn't need in ALL the tests, but I think for what you described doing it in the setUp is way better.

编辑:setUp 方式更好,我正在考虑您在所有测试中都不需要的东西,但我认为您在 setUp 中描述的方法更好。