Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save channaveer/f508948c53ba2139cf33c1d86a5a2b44 to your computer and use it in GitHub Desktop.
Save channaveer/f508948c53ba2139cf33c1d86a5a2b44 to your computer and use it in GitHub Desktop.
Test Driven Laravel from Scratch by Adam Wathan

Test Driven Laravel from Scratch

https://vimeo.com/151390908 by Adam Wathan 26 min ~2015.

One of the biggest hurdles in getting started with test driven development with a brand new application is knowing exactly what test to write first. In this screencast, I walk through using outside-in TDD to drive out a feature from scratch in a brand new untouched Laravel application.

Notes & disclaimer

  • The purpose of these notes are to follow the above tutorial, making detailed notes of each step.
  • Adams video is from ~2015, some of the commands used are different in Laravel 5.8
  • They are not verbatim of the original video.
  • Although the notes are detailed, it is possible they may not make sense out of context.
  • The notes are not intended as a replacement the video
    • Notes are more of a companion
    • They allow an easy reference search.
    • Allowing a particular video to be found and re-watched.
  • Code snippets are often used to highlight the code changed, any code prior or post the code snipped is generally unchanged from previous notes, or to highlight only the output of interest. To signify a snippet of a larger code block, dots are normally used e.g.
\\ ...
echo "Hello";
\\ ...

Notes from the Video

Adam talks about an example of how he may get started with a new application. e.g. a twitter clone.

  • Create an account
  • Post a tweet
  • View another user's tweets
  • Follow another user
  • View a list of my tweets on my timeline

A good place to start would be view another user's tweets, as this is the business outline. You don't need to be logged in, it is related to the app.

Create a test for the feature. This test is a feature test, as it will be an acceptance test.

Create a features test called ViewAnotherUsersTweetsTest.php

  • There will need to be a user
  • The user will need to have tweets
  • r-spec book describes an approach called direct model access, which includes directly creating the data in the database.
  • Laravel has model factories which allows data to be created on the fly.
  • visit the user's page, we should see the users tweet.
// ...
$user = factory(User::class)->create(['username' => 'johndoe']);
$tweet = factory(Tweet::class)->make(['body' => 'My first tweet']);

$user->tweets()->save($tweet);

$this->visit('/johndoe)
  ->see('My first tweet');
// ...

First error, the user factory doesn't exist

  • Update the User namespace for the user factory.
  • Import the namespace in the test

Second error, access denied for user 'homestead'@'localhost'..

  • Edit phpunit.xml to configure the tests to be run sqlite in memory.

Next error: There is no such table users.

  • Add DatabaseMigrations trait to the test.

Next error: Table users has not column named username

  • Edit the user migration, so there is a field username which is a string.

Next error: Table users has not column named name

  • In the model factory a name fields is being added to the database, this is just been changed to username
  • amend the UserModelFactory to username.

Next error: Unable to locate factory with name Tweet

  • The user has successfully been created!
  • Create a tweet factory.
  • use a faker sentence for the field body.

Next tweet: Class 'Tweet' not found...

  • Generate a tweet model. php artisan make:model Tweet

Next error: Class 'Tweet' not found...

  • The generated tweet is in the app namespace.

Next error: Unable to locate factory with the name Tweet

  • Import the tweet class.

Next error: Call to undefined method ... Query\Builder::tweets()

  • This is due to the tweets method, being called on the user, is not defined.
  • Any methods that do not exist on a model are automatically delegated to the query builder, using a __call magic method.
  • Open User.php class and create the relationship.
  • Create an empty public tweets() method

Next error: Call to a member function save() on null..

  • tweets() is returning null instead of the relationship.
  • Inside the tweets method return $this->hasMany(Tweets::class);

Next error: no such table tweets..

  • Generate a migration for the table php artisan make:migration create_tweets_table --create=tweets

Next error: Table tweets has no column named body

  • Open the migration add $table->string('body);

Next error: table tweets has no column named user_id

  • There needs to be a relationship back to users, tweets belong to users and users have many tweets.
  • Open the Migration for Tweets, add $table->integer('user_id')->unsigned(); (this is now unsignedBigInt)

Next error: request to [http://localhost/johndoe] failed. Received status code 404

  • This is a routing error. Open web.php, create a route.
  • Use a strategy called programming by wishful thinking. Write the code we wish we had. The route will call a controller, rather than a closure.
Route::get('{username}', 'UserController@show');

Next error: Received status code 500.. caused by ... UsersController does not exist.

  • Create a controller php artisan make:controller UsersController

Next error: Received status code 500.. caused by ... method... UsersController::show() does not exist

  • Create an empty public method called show.

Next error: Failed asserting that '' matches PCRE pattern "/My first tweet/i"

  • this means the ->see assertion has been reached in the test and it is receiving an empty string.
  • the empty show method isn't returning a view which should display the tweet.
  • add: return view('users.show'); to the show method.

Next error: ... 'view [users.show] not found'..

  • Create a folder under resources > views called users and a blade file called show.blade.php

Next error: Failed asserting that '' matches PCRE pattern "/My first tweet/i"

  • This is due to the view being empty.
  • Again write the code we wish we had.
<ul>
    @foreach ($user->tweets as $tweet)
        <li>{{ $tweet->body }}</li>
    @endforeach
</ul>

Next error: ..'Undefined variable: user'.. in the compiled view

  • The user data isn't being passed to the view from the controller.
  • In the UserController amend the view to return the user return view('users.show', ['user' => $user]);

Next error: ..Received status code 500 ... 'Undefined variable: user' in ... UsersController..

  • In the show method it could be implemented my passing in the username and looking up that username in the controller, like so:
// ...
public function show($username)
{
    $user = User::where('username', $username)->first();
    return view('users.show', ['user' => $user]);
}
  • Instead create a method in the User model, this will keep the controller 'skinny', something like this:
// ...
public function show($username)
{
    $user = User::findByUsername($username);
    return view('users.show', ['user' => $user]);
}

Next error: Class 'App....\User' not found.

  • Import the user class to the namespace reference

Next error: BadMethodCallException ... Call to undefined method ... Builder::findByUsername()

  • The method hasn't been created, yet.
  • Instead of opening the user model and adding the method, create a new unit test for this requirement.
    • Under the tests folder create a unit folder and then a UserTest.php file
    • Copy the code from the example test and rename the class UserTest:
// ...
class UserTest extends TestCase
{
    public function testAUserCanBeFoundByUsername()
    {
        $user = factory(User::class)->create('username' => 'janedoe');

        $foundUser = User::findByUserName('janedoe');

        $this->assertEquals($createdUser->id, $foundUser->id);
        $this->assertEquals($createdUser->username, $foundUser->username);
    }
}

Run this test, error: Unable to locate factory with name User.

  • Import the User class namespace

Next error: no such table: users

  • Add user DatabaseMigrations; trait

Next error: call to undefined method ... Builder::findByUsername()

  • Open User class and add an empty static method findByUsername

Next error:ErrorException: trying to get property of non-object

  • findByUsername isn't returning anything.
  • add return self::where(;username', $username)->first();

The test now passes.

Bubble back up to the acceptance test. ViewAnotherUsersTweetsTest.php

This test also passes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment