|

Fixing Broken Tests As Features Are Added

Fixing Broken Tests As Features Are Added

This tutorial will add the concept of threads having a channel for which they belong to. Here comes the challenge however. At this point, our database does not support that ability. Therefore as we start adding this feature, things are going to start to break. Since we have been building tests all along, we will know where things are breaking based on the tests that start failing. So let’s take a stab at adding in the code to support channels, see what breaks, and then fix along the way.


Refactor Two Tests Into One

Right now we have two tests that kind of do the same thing. We have test_guest_can_not_create_threads() as well as test_guest_can_not_see_thread_create_form() but they overlap just a bit. Let’s merge them into one function. First we’ll look at what we currently have and then we’ll take a look at how to refactor these two tests into one.

After a bit of refactoring, we can clean up these two tests into just one test here.

We actually don’t need the call to $this->withExceptionHandling() because it is actually on by default. The only time we need to make a call to $this->withExceptionHandling() is if we already made a call to $this->withoutExceptionHandling() and we’d like to turn it back on. So this quick refactor makes our test file just a little bit more clean. Check out the post about how to toggle exception handling to learn more.


Channels For Threads

As it stands now, when a thread post is made, it simply goes into one big bucket. There are no categories or channels to speak of. You are likely familiar with the concept of posting into a particular category when you visit a forum. We need to somehow implement this concept of channels. Each thread should belong to a channel. Following test driven development, let’s look at how to begin writing a test to build out this feature.

We can open our ThreadTest.php file which is part of our Unit Tests folder. We want to assert that a thread is an instance of Channel. In our code, that may look like so:

We can run the test using the filter flag once again to test only our new test.

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

E                                                                   1 / 1 (100%)

Time: 1.1 seconds, Memory: 8.00MB

There was 1 error:

1) Tests\Unit\ThreadTest::test_a_thread_belongs_to_a_channel
PHPUnit\Framework\Exception: Argument #1 (No Value) of PHPUnit\Framework\Assert::assertInstanceOf() must be a class or interface name

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

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

Of course we kind of expected this to fail but it’s nice to become familiar with the errors we may see in various situations. In this case, we don’t even have a Channel class yet, so of course we are going to need to create one. Let’s build that class now. We will create a new model and add the -m flag to include a migration as well.

vagrant@homestead:~/Code/forumio$ php artisan make:model Channel -m
Model created successfully.
Created Migration: 2017_12_20_225927_create_channels_table

We now have a Channel class, so let’s run the test again to see what happens.

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

F                                                                   1 / 1 (100%)

Time: 718 ms, Memory: 8.00MB

There was 1 failure:

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

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

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

The test still fails but the error message is different. This time around we see that the test has failed to assert that null is an instance of the Channel class. Ok, sounds like we are missing a relationship in the Thread class. We said that a Thread should have a Channel. In other words a thread belongs to a channel. Let’s add that relationship.

We run the test again, but we get the same error message. Well, we also need to account for the relationship in the threads database table. Every thread needs to have an associated channel_id. We need to update that migration file like so:

Heads up! We are also going to need to update our model factory for a thread to reflect the changes we have made, otherwise creating new threads for testing will fail.

We can run the test again to see if we have made any progress.

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

E                                                                   1 / 1 (100%)

Time: 1.09 seconds, Memory: 8.00MB

There was 1 error:

1) Tests\Unit\ThreadTest::test_a_thread_belongs_to_a_channel
InvalidArgumentException: Unable to locate factory with name [default] [App\Channel].

The thread factory seems to be ok, but we are missing an actual channel factory. We can create one though, so fear not! The Channel will consist of a name and a slug.

Running the tests again will show us how things are progressing.

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

E                                                                   1 / 1 (100%)

Time: 1.03 seconds, Memory: 8.00MB

There was 1 error:

1) Tests\Unit\ThreadTest::test_a_thread_belongs_to_a_channel
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1 table channels has no column named name (SQL: insert into "channels" ("name", "slug", "updated_at", "created_at") values (error, error, 2017-12-21 00:01:17, 2017-12-21 00:01:17))

We are getting an error about a missing column name in the database table. This make sense as we never did build up our table in the migration for a channel. We need to fix this. Go ahead and open up the CreateChannelsTable migration and we can update the file as we see here:

Let’s run that test one more time. I think we might have better luck!

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

.                                                                   1 / 1 (100%)

Time: 992 ms, Memory: 8.00MB

OK (1 test, 1 assertion)

Finally! This is a good demonstration of the test driven development cycle. You fix one error at a time until you finally get the test to pass.


Checking All Tests

We got this new feature of a Channel incorporated into our tests, and that particular test passes. We should probably run the entire suite of tests now to make sure everything is still held together.

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

.F...........                                                     13 / 13 (100%)

Time: 1.95 seconds, Memory: 14.00MB

There was 1 failure:

1) Tests\Feature\CreateThreadsTest::test_a_logged_in_user_can_create_new_threads
Failed asserting that (the html) contains "Qui dolorem laborum reiciendis ut.".

We see that the test_a_logged_in_user_can_create_new_threads() is now failing. Why? It fails because now we need a new Thread to have an associated channel_id, but this is not yet in place so we need to fix this. This will be in the store() method of the ThreadsController. Here we add a reference to the channel_id when creating a new thread.

Run all tests again.

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

.............                                                     13 / 13 (100%)

Time: 1.74 seconds, Memory: 10.00MB

OK (13 tests, 19 assertions)

Yes! All tests are passing again.


Updating The String Path

We’ll need to account for the fact that the current path() method is only providing for the id of a thread. It must also account for our new ability to include a channel for each thread. We can start off by creating a test for this. We will add this in our ThreadTest class.

What we are doing with the test highlighted above is to say that given a thread, we want to assert that the actual path is equal to the path produced by $thread->path(). Let’s test it out and see how it goes. We can filter our tests with the –filter flag to just run the ThreadTest class.

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

F....                                                               5 / 5 (100%)

Time: 1.54 seconds, Memory: 8.00MB

There was 1 failure:

1) Tests\Unit\ThreadTest::test_a_thread_can_make_a_string_path
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'/threads/perferendis/2'
+'/threads/2'

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

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

Ok, this is helpful. We can see that the expected result is ‘/threads/perferendis/2’ but we are actually getting the result of ‘/threads/2’. We can update the $thread->path() function so that we get the result we expect.

With this update to the path() function, our test is now passing. Fantastic.

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

.....                                                               5 / 5 (100%)

Time: 1.09 seconds, Memory: 8.00MB

OK (5 tests, 5 assertions)

Refresh The Local Database

At this point, the schema of the database has changed – so our existing data in the local database is no longer going to work. Our tests are passing, however this is due to the fact that we are using an SQLite in memory database for testing. So when we run our tests, we are actually rebuilding everything in the database on each test. Once the test finishes, everything get’s rolled back. That is pretty neat when you think about it! In any event, let’s go ahead and fix the local database.

vagrant@homestead:~/Code/forumio$ php artisan migrate:refresh
Rolling back: 2017_12_05_185134_create_replies_table
Rolled back:  2017_12_05_185134_create_replies_table
Rolling back: 2017_12_05_174156_create_threads_table
Rolled back:  2017_12_05_174156_create_threads_table
Rolling back: 2014_10_12_100000_create_password_resets_table
Rolled back:  2014_10_12_100000_create_password_resets_table
Rolling back: 2014_10_12_000000_create_users_table
Rolled back:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table
Migrating: 2017_12_05_174156_create_threads_table
Migrated:  2017_12_05_174156_create_threads_table
Migrating: 2017_12_05_185134_create_replies_table
Migrated:  2017_12_05_185134_create_replies_table
Migrating: 2017_12_20_225927_create_channels_table
Migrated:  2017_12_20_225927_create_channels_table

vagrant@homestead:~/Code/forumio$ php artisan tinker
Psy Shell v0.8.16 (PHP 7.1.2-3+deb.sury.org~xenial+1 รข cli) by Justin Hileman
>>> factory('App\Thread', 50)->create();

So what we did here was to refresh all the database tables which causes all data to be destroyed in the process. Therefore, we also had to create 50 new threads in Tinker. If we visit the /threads route in a browser, we get to see all of our new threads!
new threads after migrate refresh

Clicking on a link however no longer works because we have no route that accounts for threads with channels.
404 not found

Time to visit the routes file and account for the channel. Now, along the way, we had reduced the number of route endpoints in our routes file by using a resource such as Route::resource(‘threads’, ‘ThreadsController’). We’re actually going to roll that back and move to individual routes for each endpoint now. The route that is highlighted now takes into account a channel as part of the URI.

Since our route for the show method on the ThreadsController has been updated to account for a channel, the show method in the controller must also be updated. Basically, we just need to account for the channel id as a parameter like we see here:

Now might be a good time to run our tests to see if anything is broken.

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

.E...EE.......                                                    14 / 14 (100%)

Time: 1.93 seconds, Memory: 10.00MB

There were 3 errors:

1) Tests\Feature\CreateThreadsTest::test_a_logged_in_user_can_create_new_threads
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: GET http://localhost/threads/sunt

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php:107
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:326
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:120
/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:168
/home/vagrant/Code/forumio/tests/Feature/CreateThreadsTest.php:29

2) Tests\Feature\ReadThreadsTest::test_a_user_can_read_a_single_thread
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: GET http://localhost/threads/1

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php:107
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:326
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:120
/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:168
/home/vagrant/Code/forumio/tests/Feature/ReadThreadsTest.php:29

3) Tests\Feature\ReadThreadsTest::test_a_user_can_see_replies_that_are_associated_with_a_thread
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: GET http://localhost/threads/1

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php:107
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:326
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:120
/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:168
/home/vagrant/Code/forumio/tests/Feature/ReadThreadsTest.php:37

ERRORS!
Tests: 14, Assertions: 15, Errors: 3.

Whoa Nelly! Now we’ve done it. 3 tests are failing, so let’s try to fix them one at a time.

For the test_a_logged_in_user_can_create_new_threads test, we need to go from make() to create() in order to persist to the database like so.

Now this test passes, one down – two to go.

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

.                                                                   1 / 1 (100%)

Time: 1.2 seconds, Memory: 10.00MB

OK (1 test, 1 assertion)

Ok next up is the test_a_user_can_read_a_single_thread test. Let’s see how we can fix this one. Ah yes, we need to account for the channel on this one as well.

And we are good on this one too.

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

.                                                                   1 / 1 (100%)

Time: 911 ms, Memory: 10.00MB

OK (1 test, 2 assertions)

The last test we need to fix is test_a_user_can_see_replies_that_are_associated_with_a_thread. So let’s see what went wrong there.

Once again we had to account for the channel. Now all tests are passing again, so great work!

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

..............                                                    14 / 14 (100%)

Time: 1.91 seconds, Memory: 10.00MB

OK (14 tests, 20 assertions)

Danger! Sometimes Tests Pass When They Should Not

This test should actually be failing since it does not reflect the channel.

Ok, it might be because the path is hardcoded in the post request. If we update that to make use of $thread->path() it will probably fail like we expect. Yes this does appear to be the case now.

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

...E..........                                                    14 / 14 (100%)

Time: 1.94 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: POST http://localhost/threads/voluptatem/1/replies

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php:107
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:326
/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:120
/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:27

ERRORS!
Tests: 14, Assertions: 19, Errors: 1.

What this means is that we need to update the routes file for storing new replies, in addition to updating the store method on the RepliesController.

Now we are good!

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

.                                                                   1 / 1 (100%)

Time: 917 ms, Memory: 10.00MB

OK (1 test, 2 assertions)

We need to run the full suite of tests again to see where we are at, and oh no, we still have something failing.

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

..F...........                                                    14 / 14 (100%)

Time: 2.06 seconds, Memory: 12.00MB

There was 1 failure:

1) Tests\Feature\ParticipateInForumTest::test_unauthenticated_users_can_not_add_replies
Failed asserting that exception of type "Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException"

Once again we can update that test to account for the channel like so.

Finally, one last run of the full suite of tests, and everything passes.

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

..............                                                    14 / 14 (100%)

Time: 1.92 seconds, Memory: 10.00MB

OK (14 tests, 21 assertions)

Fixing Broken Tests As Features Are Added Summary

If you made it to the end of this tutorial, you are a brave person! We can see how once things start breaking, we need to take the time to examine where the failures are, and fix the problems one at a time. It is a bit of a tedious process, but gratifying once you reach the point of all tests passing again.