Laravel TDD Session – PHPUnit Integration – Part 2

Laravel TDD Session – PHPUnit Integration – Part 2

Introduction

Test driven development is a a way of programming where we write test cases first and then we write code to pass those test cases.This is common in all programming languages. PHP is not an exception. PHP have library called PHPUnit. It provides facility to write test cases for any PHP application.

This is second post in the series of test drive development with Laravel or Laravel with TDD. Part one of the series introduced you for environment setup and setting a basic application to get start with.

Writing Test Case With PHPUnit

Writing test cases with PHPUnit is not a rocket science. Its simple way of thinking out of the box before writing any actual code of application. You collects your input and assumes that you will have a function which will that those inputs and produces some output.

Process

  1. Think of any scenario for your application.
  2. Think of a function or a class.
  3. Collect all your inputs.
  4. Find expected output
  5. Write a test case with collected input and check if your created function produces same output.
  6. Fail the test for first time.
  7. Create that actual function in your application.
  8. Run test cases again and pass those test cases for you function.
  9. If your function is still failing test cases, then you need to fix up your code as per your requirements.

Coding Section

Enough talking, lets get into the code and write our own test cases with PHPunit in Laravel. If you do not understand all of this, you can first read my post about Getting Started. If you are familiar with and missed first part of this series you can find it at Laravel TDD Session – Create An Application in Laravel – Part 1.

Write First test case

Now your Project set up is done. So let’s get start with writing test cases, and creating application. So we will create or first feature test case a_user_can_view_threads().

Go to tests directory, in features you can find ExampleTest.php. Rename that to ThreadsTest.php.  Delete everything inside the class and add following code

/**
 * @test
 */
public function a_user_can_browse_threads()
{
    $response = $this->get('/threads');

    $response->assertStatus(200);
}

That’s it. You did it. You have written a test case with PHPunit. It’s super easy to do. This test case is very simple, It will go to /threads route and will check if it access it or not? If it’s running it should get a status code of 200.

Now, you must be having some questions like:

  1. How will I run these test cases?
  2. Is it going to affect my database?
  3. How can write more test cases?

Don’t worry. We are going to talk about every single step in this post.

Connection with a database

Here I am going to tell you a trick for not messing up your application database. If you are a bit late in the test driven development and your application is running in production. You don’t want to mess up your whole application. Hence, PHPUnit and Laravel both works perfectly in these cases. This is a life savior for you.

While running test cases, Laravel and PHPUnit provides a way to enable a different test database for testing your application. While you application is running in production. Let’s see how can we do it?

Add following code in PHP section of phpunit.xml. You can find it in root of your project directory.

<php>
    <env name="APP_ENV" value="testing"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
    <env name="CACHE_DRIVER" value="array"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="QUEUE_DRIVER" value="sync"/>
</php>

What it does?

It will create a connection to SQLite database. And will run test cases in memory.  What is in memory means? In memory means it will run migrations first. Run your test cases based on those tables. then it will revert it back. So it won’t touch your production system at all.

To achieve migration functionality add DBMigrations Trait in ThreadsTest.php

use DatabaseMigrations;

Running Test Cases

Now, you are ready with your test cases and database setup. It’s time to run that test case.

Go to terminal and change directory to your project root directory. Type following command in your terminal:

$ phpunit

Failed Test Case

Boom! Red lines. What just happened? Your test case failed. That’s exactly what we wanted. As we do not have any code written yet for it. So this test cases is failing. Let’s write code for passing this test case.

Passing Test Case

I like to keep my controllers name plural. So I have renamed all my controller names to plural. This is option if you want to keep it singular, you can. Please adjust your code as per your requirement.  I have done this , ThreadController to ThreadsController and ReplyController to RepliesController.

Create a route

Create a route in web.php

Route::get('/threads', 'ThreadsController@index');

Add code in ThreadsController

Change in ThreadsController:index()

public function index()
{
    $threads = Thread::latest()->get();
    return view('threads.index', compact('threads'));
}

Create a view

Create a blade file in resources/views/threads/index.blade.php

Generating Auth

Before we start writing any code in blade file, lets generate some scaffolding for our Laravel application. Laravel makes it simple for us by providing make:auth command.

Generate basic scaffolding and auth by:

$ php artisan make:auth

Creating view

Add following code to index.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Forum Threads</div>

                    <div class="panel-body">
                        @foreach($threads as $thread)
                            <article>
                                <h4>{{ $thread->title }}</h4>
                                <div class="body">{{ $thread->body }}</div>
                            </article>
                        @endforeach
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Run it again

Now run phpunit in terminal. Your output will be

Passed test case

Green! yeah , we have passed our first test case. You can test in your browser also.  So this is all you needed to do. Now you can any number of test cases as per your requirement.

Run the application, you can see in browser.

Browser output

What’s next?

Your test cases are running. You can see results n browser as well as in your terminal. So what’s next step. Now you can go back and start writing test cases or you can follow along to get more understanding.

Currently our test case is just checking for status code 200. But it doesn’t check for if we really getting any data on that page.

Lets write our test case to check for content. Change code of ThreadsTest

/**
 * @test
 */
public function a_user_can_browse_threads()
{
    $thread = factory(Thread::class)->create();
    $response = $this->get('/threads');

    $response->assertSee($thread->title);
}

Run phpunit

Assertion for title

Now, it’s checking for the content on the page from database.

Writing more test cases.

I have explained every single detail. I don’t want to make this post hard to read. To keep it simple here are some more test cases with all the steps and code. You can follow them and do it by yourself.  If you face any problems feel free to comment.

Change ThreadsTest.php

/**
 * @test
 */
public function a_user_can_browse_threads()
{
    $thread = factory(Thread::class)->create();
    $response = $this->get('/threads');

    $response->assertSee($thread->title);

    $response = $this->get('/threads/' . $thread->id);

    $response->assertSee($thread->title);
}

Run phpunit and it will fail. as we don’t have any route.

Add route

Route::get('/threads/{thread}', 'ThreadsController@show');

Add following to ThreadsController

/**
 * Display the specified resource.
 *
 * @param  \App\Thread $thread
 * @return \Illuminate\Http\Response
 */
public function show(Thread $thread)
{
    return view('threads.show', compact('thread'));
}

Create show.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">{{ $thread->title }}</div>

                    <div class="panel-body">
                        {{ $thread->body }}
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Run in browser

Single thread browser output

Run phpunit

Single thread test case passed

Refactoring ThreadsTest.php, here is final file

<?php

namespace Tests\Feature;

use App\Thread;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ThreadsTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * @test
     */
    public function a_user_can_view_all_threads()
    {
        $thread = factory(Thread::class)->create();
        
        $response = $this->get('/threads');
        $response->assertSee($thread->title);
    }

    /**
     * @test
     */
    public function a_user_can_read_a_single_thread()
    {
        $thread = factory(Thread::class)->create();

        $response = $this->get('/threads/' . $thread->id);
        $response->assertSee($thread->title);
    }
}

Add following in Thread.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Thread
 * @package App
 */
class Thread extends Model
{
    /**
     * @return string
     */
    public function path()
    {
        return '/threads/' . $this->id;
    }
}

Final threads/index.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Forum Threads</div>

                    <div class="panel-body">
                        @foreach($threads as $thread)
                            <article>
                                <h4>
                                    <a href="{{ $thread->path() }}">
                                        {{ $thread->title }}
                                    </a>
                                </h4>
                                <div class="body">{{ $thread->body }}</div>
                            </article>
                            <hr/>
                        @endforeach
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Conclusion

if you have made so far. You are ready to go and create test cases. I will be posting more on it in my next upcoming articles.

What can you do?

If you faced any problem in running code, you can post it in comments section. You can reach out to me on Facebook at Codeistalk.

You can send me an email on codeistalk@gmail.com.

If you liked this you can share with your friends and team mates. Send me an email how you feel about it, or comment on post or you can post on Facebook page.

Laravel TDD Session – Create An Application in Laravel – Part 1

Laravel TDD Session - Create An Application in Laravel With TDD - Part 1

Introduction

Hi, Laravel TDD or Test Driven Development with Laravel is a hot topic today. This is the first article in the series of Laravel TDD. If you are new to Laravel TDD and want to get start. Then you are at right place.

If you are beginner in Laravel and don’t know where to start. You can read me previous article Laravel Beginners Session With MySQL and MongoDB – Part 1. This will help you setup a fresh Laravel application with step by step process.

If you are an intermediate or experienced Laravel developer and want to know how to get start Test Driven Development or TDD in Laravel. You can get start here.

Hard code developer section

Make a fresh Laravel installation

$ composer create-project --prefer-dist laravel/laravel forum

Change directory to project root

$ cd forum

Create a model, migration and resourceful controller

$ php artisan make:model Thread -mr

Now, go to thread migration and edit up() function with following code

public function up()
 {
     Schema::create('threads', function (Blueprint $table) {
         $table->increments('id');
         $table->integer('user_id');
         $table->string('title');
         $table->text('body');
         $table->timestamps();
     });
 }

Optional Section

Install Laravel 5 IDE Helper Generator

$ composer require --dev barryvdh/laravel-ide-helperlaravel

Install doctrine/dbal package

$ composer require doctrine/dbal

Add following code to register() method of app/Providers/AppServiceProvider.php

public function register()
 {
     if ($this->app->environment() !== 'production') {
         $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
     }
 }

Continue From Here…

Open MySQL database and create a new database called forum.

$ create database forum

Open .env file from Laravel project and edit your database configuration. Go to terminal and run migration

$ php artisan migrate

Create a model Reply from your terminal by running following command:

$ php artisan make:model Reply -mr

Open migration file of replies and modify up() method with following code:

public function up()
{
    Schema::create('replies', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('thread_id');
        $table->integer('user_id');
        $table->text('body');
        $table->timestamps();
    });
}

Run migration with following command:

$ php artisan migrate

Open database/factories/ModelFactory.php and add following code:

$factory->define(\App\Thread::class, function ($faker) {
    return [
        'user_id' => function () {
            return factory(\App\User::class)->create()->id;
        },
        'title' => $faker->sentence,
        'body' => $faker->paragraph
    ];
});

Laravel Tinker – Your New Friend

Open terminal and run following command:

$ php artisan tinker

It will give you access to Laravel’s tinker command. We shall use tinker for generating seeding data for our threads model. Run following command:

$ factory('App\Thread', 50);

It will fill 50 users and thread records in your database.

Add following code to your ModalFactory.php

$factory->define(\App\Reply::class, function ($faker) {
    return [
        'thread_id' => function () {
            return factory(\App\Thread::class)->create()->id;
        },
        'user_id' => function () {
            return factory(\App\User::class)->create()->id;
        },
        'body' => $faker->paragraph
    ];
});

Refresh your migration

$ php artisan migrate:refresh

This will delete all the seed data generated in previous step. So now run tinker below commands to generate Users, Threads and Replies

$ php artisan tinker
$ thread = factory('App\Thread', 50)->create(); 
$ $thread->each(function ($thread) { factory('App\Reply', 10)->create(['thread_id' => $thread->id]); });

First command will get you into Laravel tinker.

Second command will generate 50 users and threads in database and will assign thread object to $thread.

Third command will generate 10 replies for each thread.

This is the first part of series. We have a Laravel application with database setup and running. We will continue this in next post as we go along.

If you have any questions, post in comments section. If you like it share it with your friends and team.