|

Using Test Authentication To Allow Logged In Users To Post Replies

Using Test Authentication To Allow Logged In Users To Post Replies

We’ll keep plugging away at our code in this laravel tutorial and tackle many things. Adding links to user profiles is on the table as is cleaning up our view files by making use of partials. From there we’ll move on to adding support for displaying thread owners backed by tests. We’ll add a new testing class of ParticipateInForumTest to start testing and building the concept of authenticated users adding replies to threads. Last up, we’ll take a quick look at adding a simple middleware to protect an endpoint from non authenticated users.


Linking To A User Profile

Recall that in the last episode, we set up the show.blade.php view for viewing a specific thread. On that page, we see all of the replies with the name of the person who submitted the reply. It might be nice to link to the profile of the user who submitted a reply, so let’s go ahead and add that to our markup now. We’ll just link to a # for now since we don’t yet actually have profiles in the application.

linking to user profiles


Cleaning Up View Files With Partials

With in the resources/views/threads directory, we can create a new file of reply.blade.php. In this file, we can extract some of the markup in our show.blade.php to make things a little nicer. Here is code in the new partial file, as well as the updated show.blade.php file which now makes use of an include to bring in the partial.


Adding Support To Display Thread Owners

When we view a thread page, we see the name of the users who post replies, but not the name of the user who posted the thread. Let’s add support for this here. So we will begin by building unit tests for Threads. First we can create the new ThreadTest class, then add the individual tests (methods of that class) once in place.

vagrant@homestead:~/Code/forumio$ php artisan make:test ThreadTest --unit
Test created successfully.

In the snippet above, we add a test for confirming that a thread has replies. We should have added this already but no worries. Running it real quick with the –filter option of phpunit shows that it is working.

vagrant@homestead:~/Code/forumio$ phpunit --filter ThreadTest
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 1.24 seconds, Memory: 8.00MB

OK (1 test, 1 assertion)

Another test we want to add is the fact that a thread has a creator. Let’s go ahead and add a test for that. The pseudo thinking for this test is: Given I have a thread, when I fetch the owner of that thread, we can assert that it is an instance of User.

Let’s run that test in isolation using the –filter flag now.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_thread_has_a_creator
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 1.15 seconds, Memory: 8.00MB

There was 1 failure:

1) Tests\Unit\ThreadTest::test_a_thread_has_a_creator
Failed asserting that null is an instance of class "App\User".

/home/vagrant/Code/forumio/tests/Unit/ThreadTest.php:24

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

As expected, this test if failing since we don’t have the plumbing in place to support this feature yet. Let’s build out the code and make this test pass.

Run our test again, and we are good to go.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_thread_has_a_creator
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 852 ms, Memory: 8.00MB

OK (1 test, 1 assertion)

Pretty cool! Now what this means is that we should be able to update our view file to reference the owner of a thread, and it is not going to blow up on us when we go to view it in the browser. Let’s test that theory out.

Viewing it in the browser, we can see it works great as we see Dr. Austyn Dicki PhD made a thread post.
thread has an owner now working


Create A New Test For Users Participating In The Forum

So far all of our tests have been focused on browsing and reading threads in the forum. We need some tests to support users participating in the forum. Once again, we can use artisan to build out the scaffolding for us. This will be a Feature Test.

vagrant@homestead:~/Code/forumio$ php artisan make:test ParticipateInForumTest
Test created successfully.

What do we want to accomplish with this test? Here is the pseudo code to help us along.

  • Given there is an authenticated user
  • Given an existing thread
  • When the user adds a reply to a thread
  • The user reply should be seen on the page

Translating the pseudo code above into a real test may look like this:

Ok let’s run the test, and it should of course fail.

vagrant@homestead:~/Code/forumio$ phpunit
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

F.......                                                            8 / 8 (100%)

Time: 1.97 seconds, Memory: 10.00MB

There was 1 failure:

1) Tests\Feature\ParticipateInForumTest::test_an_authenticated_user_can_participate_in_forum_threads
Failed asserting that (the html) contains "In autem at consequatur minima et. Quia qui fugiat ducimus. Eveniet mollitia quam doloremque ipsum.".

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:236
/home/vagrant/Code/forumio/tests/Feature/ParticipateInForumTest.php:23

FAILURES!
Tests: 8, Assertions: 11, Failures: 1.

This is interesting. The test is failing, but it is not failing until the last line of the test. It actually should fail even sooner than this. If we look at line 21, the test is making a post request to an endpoint that doesn’t even exist. Should the test fail there? Here is how to fix that little glitch. Visit the Handler.php file in our project, and add the highlighted code as seen here. (In Laravel 5.5 you can now toggle exception handling).

When we then run the test, we do in fact catch that error when trying to send a post request to a non existent endpoint.

vagrant@homestead:~/Code/forumio$ phpunit
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

E.......                                                            8 / 8 (100%)

Time: 1.69 seconds, Memory: 10.00MB

There was 1 error:

1) Tests\Feature\ParticipateInForumTest::test_an_authenticated_user_can_participate_in_forum_threads
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php:179
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Router.php:612
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Router.php:601
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Router.php:590
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:176
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:30
/home/vagrant/Code/forumio/vendor/fideloper/proxy/src/TrustProxies.php:56
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:149
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php:30
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:149
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php:30
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:149
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php:27
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:149
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php:46
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:149
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:102
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:151
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:116
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:345
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:195
/home/vagrant/Code/forumio/tests/Feature/ParticipateInForumTest.php:21

ERRORS!
Tests: 8, Assertions: 10, Errors: 1.

Build Out The New Feature

Ok. Now that we have our test in place, and the gotchas ironed out, we can build out the feature. The first thing we need for a user to post a reply to a thread, is an endpoint (or route) to send the reply to. We use a POST request in this type of scenario so let’s build out our routes file as such.

A quick note about the name of the controller and method we chose for this new route. We could just as easily added a new method to the ThreadsController like addReply() or some such method. It actually makes more sense to stick to the restful methods of index, create, store, show, edit, update, or destroy. The thought behind this is if you keep adding methods willy nilly to a single controller, that controller is going to get out of hand and you will cause pain in trying to maintain it. So with that thought in mind, let’s begin building out the RepliesController that we had created in an earlier tutorial.

So there is a little magic happening here that we should quickly discuss. First off, we are making use of route model binding, which basically gets the model from the database by simply type hinting it on the store() method. Pretty neat stuff. Now, in addition to this, we are making use of a method addReply() that does not yet exist on that model. We’ll start by adding a test to our ThreadTest class.

Let’s quickly run the test and see if we get the expected failure.

vagrant@homestead:~/Code/forumio$ phpunit --filter ThreadTest
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

..E                                                                 3 / 3 (100%)

Time: 894 ms, Memory: 8.00MB

There was 1 error:

1) Tests\Unit\ThreadTest::test_a_thread_can_add_a_reply
BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::addReply()

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2459
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:1273
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1455
/home/vagrant/Code/forumio/tests/Unit/ThreadTest.php:34

ERRORS!
Tests: 3, Assertions: 2, Errors: 1.

That looks about right, the test fails because we are making a call to a method that does not yet exist. Let’s go ahead and create it now.

Ok great! It looks like everything is in place so let’s run the test again and see how it goes.

vagrant@homestead:~/Code/forumio$ phpunit --filter ThreadTest
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

..E                                                                 3 / 3 (100%)

Time: 880 ms, Memory: 8.00MB

There was 1 error:

1) Tests\Unit\ThreadTest::test_a_thread_can_add_a_reply
Illuminate\Database\Eloquent\MassAssignmentException: body

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:232
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:152
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:275
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php:276
/home/vagrant/Code/forumio/app/Thread.php:26
/home/vagrant/Code/forumio/tests/Unit/ThreadTest.php:34

ERRORS!
Tests: 3, Assertions: 2, Errors: 1.

Oh boy. It looks like we are getting a mass assignment exception. If you are not familiar, what this means is Laravel is protecting your application from getting rogue data inserted into the database when it is not supposed to happen. Mass assignment is a whole lesson in itself, but for now we can just tell Laravel to accept anything. We do this by setting the following property: protected $guarded = []; in the model of both Thread and Reply. Once we update both of those models to allow any data to be inserted, we can run the test again and it passes.

vagrant@homestead:~/Code/forumio$ phpunit --filter ThreadTest
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 866 ms, Memory: 8.00MB

OK (3 tests, 3 assertions)

With our model working, we can go back to the Controller and add a quick redirect back function.


Prevent Non Authenticated Users From Posting

We only want users that are logged in to our application to be able to post replies to threads. In our ParticipateInForumTest class we do have a test for authenticated users, but not for unauthenticated users. Let’s create a new test to cover that now.

In the test function test_unauthenticated_users_can_not_add_replies, you can kind of read it backwards. Basically, if a post request is made to the given endpoint, it should fail with an authentication exception. Let’s run the test and see what happens.

vagrant@homestead:~/Code/forumio$ phpunit
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

F.........                                                        10 / 10 (100%)

Time: 1.5 seconds, Memory: 10.00MB

There was 1 failure:

1) Tests\Feature\ParticipateInForumTest::test_unauthenticated_users_can_not_add_replies
Failed asserting that exception of type "Illuminate\Database\Eloquent\ModelNotFoundException" matches expected exception "Illuminate\Auth\AuthenticationException". Message was: "No query results for model [App\Thread]."

So our new test is not passing. Why is this? Well, we actually haven’t implemented any type of protection on the controller to prevent unauthenticated users from accessing various endpoints. To do this, we can add a middleware. Open up the RepliesController file and add as follows.

With this middleware protection in place, all tests now pass again. Nice!

vagrant@homestead:~/Code/forumio$ phpunit
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

..........                                                        10 / 10 (100%)

Time: 1.47 seconds, Memory: 10.00MB

OK (10 tests, 13 assertions)

Using Test Authentication To Allow Logged In Users To Post Replies Summary

This was a nice tutorial that introduced the concept of testing whether a user of our application has a logged in user, and if that user can or can not post replies to threads in our forum application. In addition, we did some nice general clean up of some view files.