Laravel unit testing of controllers

前端 未结 1 1213

I am trying to start a new Laravel app following TDD

My first step is to check that the /login controller is called on the home url.

Despite following several tu

1条回答
  •  南方客
    南方客 (楼主)
    2021-02-04 06:29

    Update, 2020-07-01:

    Since this answer seems to still get some upvotes every now and then, I just want to point out that I do not consider this to be a good testing approach anymore. Laravel has massively improved on the testing experience since v4 and there was a pretty significant overall paradigm shift, away from units and classes and more towards features and endpoints. That is not only an idiomatic change but also seems to make way more sense from a technical perspective.

    Also, a lot of new and useful testing helpers that allow for less brittle tests have been introduced since then. Please refer to the documentation for an overwiew and basic test examples.


    Ok, as already explained a little in the comments, let's first take a step back and think about the scenario.

    "My first step is to check that the /login controller is called on the home url."

    So that means: When the user hits the home route, you want to check if the user is logged in. If he's not, you want to redirect them to the login, maybe with some flash message. After they have logged in, you want to redirect them back to the home page. If the login fails, you want to redirect them back to the login form, maybe also with a flash message.

    So there are several things to test now: The home controller and the login controller. So following the TDD spirit, let's create the tests first.

    Note: I'll follow some naming convention that is used by phpspec, but don't let that bother you.

    class HomeControllerTest extends TestCase
    {
        /**
         * @test
         */
        public function it_redirects_to_login_if_user_is_not_authenticated()
        {
            Auth::shouldReceive('check')->once()->andReturn(false);
    
            $response = $this->call('GET', 'home');
            
            // Now we have several ways to go about this, choose the
            // one you're most comfortable with.
    
            // Check that you're redirecting to a specific controller action 
            // with a flash message
            $this->assertRedirectedToAction(
                 'AuthenticationController@login', 
                 null, 
                 ['flash_message']
            );
            
            // Only check that you're redirecting to a specific URI
            $this->assertRedirectedTo('login');
    
            // Just check that you don't get a 200 OK response.
            $this->assertFalse($response->isOk());
    
            // Make sure you've been redirected.
            $this->assertTrue($response->isRedirection());
        }
    
        /**
         * @test
         */
        public function it_returns_home_page_if_user_is_authenticated()
        {
            Auth::shouldReceive('check')->once()->andReturn(true);
    
            $this->call('GET', 'home');
    
            $this->assertResponseOk();
        }
    }
    

    And that's it for the Home controller. In most cases you actually don't care where you are redirected to, because that may change over time and you would have to change the tests. So the least you should do is to check whether you are being redirected or not and only check for more details if you really think that it matters for your test.

    Let's have a look at the Authentication controller:

    class AuthenticationControllerTest extends TestCase
    {
        /**
         * @test
         */
        public function it_shows_the_login_form()
        {
            $response = $this->call('GET', 'login');
    
            $this->assertTrue($response->isOk());
    
            // Even though the two lines above may be enough,
            // you could also check for something like this:
    
            View::shouldReceive('make')->with('login');
        }
    
        /**
         * @test
         */
        public function it_redirects_back_to_form_if_login_fails()
        {
            $credentials = [
                'email' => 'test@test.com',
                'password' => 'secret',
            ];
    
            Auth::shouldReceive('attempt')
                 ->once()
                 ->with($credentials)
                 ->andReturn(false);
    
            $this->call('POST', 'login', $credentials);
    
            $this->assertRedirectedToAction(
                'AuthenticationController@login', 
                null, 
                ['flash_message']
            );
        }
    
        /**
         * @test
         */
        public function it_redirects_to_home_page_after_user_logs_in()
        {
            $credentials = [
                'email' => 'test@test.com',
                'password' => 'secret',
            ];
    
            Auth::shouldReceive('attempt')
                 ->once()
                 ->with($credentials)
                 ->andReturn(true);
    
            $this->call('POST', 'login', $credentials);
    
            $this->assertRedirectedTo('home');
        }
    }
    

    Again, always think about what you really want to test. Do you really need to know which controller action is triggered on which route? Or what the name of the view is that is returned? Actually, you just need to make sure that the controller actually attempts to do it. You pass it some data and then test if it behaves as expected.

    And always make sure you're not trying to test any framework functionality, for instance if a specific route triggers a specific action or if a View is loaded correctly. This has already been tested, so you don't need to worry about that. Focus on the functionality of your application and not on the underlying framework.

    0 讨论(0)
提交回复
热议问题