Laravel 5.1 Beauty - Testing

Starting the l5beauty project and doing just a bit of testing

Posted on March 22, 2015 in L5 Beauty
Note this is the fourth step of the tutorial.

In this chapter we’ll create a project to use throughout the rest of the book and explore various options for testing. Along the way a service class to convert Markdown formatted text files to HTML will be developed.

Chapter Contents

Creating the l5beauty Project

Following the Six Steps to Starting a New Laravel 5.1 Project from the previous chapter, create the l5beauty project as outlined below.

First, from your Host OS, install the app skeleton.

Step 1 - Install the app skeleton

~/Code % laravel new l5beauty
Crafting application...
Generating optimized class loader
Compiling common classes
Application key [rzUhyDksVxzTXFjzFYiOWToqpunI2m6X] set successfully.
Application ready! Build something amazing.

Next, from within the Homestead VM, set up l5beauty.app as the virtual host.

Step 2 - Configure the web server

~/Code$ serve l5beauty.app ~/Code/l5beauty/public
dos2unix: converting file /vagrant/scripts/serve.sh to Unix format ...
 * Restarting nginx nginx                                                [ OK ]
php5-fpm stop/waiting
php5-fpm start/running, process 2169

Back in your Host OS, add the following line to your hosts file.

Step 3 - Add l5beauty.app to Your Hosts File

192.168.10.10  l5beauty.app

From your Host OS, do the step to install the NPM packages locally.

Step 4 - NPM Local Installs

~% cd Code/l5beauty
~/Code/l5beauty% npm install
|
> node-sass@2.0.1 install /Users/chuck/Code/l5beauty/node_modules/laravel-\
    elixir/node_modules/gulp-sass/node_modules/node-sass
> node scripts/install.js


> node-sass@2.0.1 postinstall /Users/chuck/Code/l5beauty/node_modules/\
    laravel-elixir/node_modules/gulp-sass/node_modules/node-sass
> node scripts/build.js

`darwin-x64-node-0.10` exists; testing
Binary is fine; exiting
gulp@3.8.11 node_modules/gulp
├── v8flags@2.0.2
├── pretty-hrtime@0.2.2

[snip]

Go back within the Homestead VM and create the database for this project.

Step 5 - Create the app’s database

$ mysql --user=homestead --password=secret
mysql> create database l5beauty;
Query OK, 1 row affected (0.00 sec)

mysql> exit;
Bye

Then edit the .env file, changing the database to l5beauty.

Changing DB_NAME in configuration

// Change the following line
DB_DATABASE=homestead

// To the correct value
DB_DATABASE=l5beauty

Finally, bring up http://l5beauty.app in your browser to make sure everything is working correctly.

Figure 6.1 - Step 5 - Testing in the Browser

Default Laravel Page

Running PHPUnit

Laravel 5.1 comes out of the box ready for testing. There’s even a very simple unit test supplied to make sure a web request to the application returns the expected 200 HTTP response.

To run PHPUnit, simply execute the phpunit command from the project’s root directory.

Running PHPUnit

~% cd Code/l5beauty
~/Code/l5beauty% phpunit
PHPUnit 4.7.4 by Sebastian Bergmann and contributors.

.

Time: 544 ms, Memory: 10.25Mb

OK (1 test, 2 assertions)

Did you get an error?

If you receive a command not found or a permissions denied error when attempting to run the phpunit command it could be because of an installation bug. The phpunit command should be found in the vendor/bin directory—and this directory was added to the path in your Host OS back in Chapter 3 or 4. The problem is that the Laravel command has a bug that doesn’t necessarily set the permissions correctly on phpunit and several other utilities.

To fix this bug, follow the two steps below.

Step 1 - Delete the vendor directory. Just wipe it out using whatever command is appropriate for your Host OS.

Step 2 - Recreate the vendor directory using the composer update command from your project’s root directory. (Do this from your Host Operating System.)

That’s it. Then try executing the phpunit command again.

Laravel 5.1’s PHPUnit Configuration

In the root of each Laravel 5.1 project is the file phpunit.xml. This contains the configuration PHPUnit uses when phpunit is executed from the project’s root directory.

Examination of the phpunit.xml will show the tests reside within the tests directory. There are two files located there.

  1. ExampleTest.php - Contains one test testBasicExample(). The ExampleTest class is derived from the TestCase parent provided in the other file.
  2. TestCase.php - The base class from which to derive Laravel tests.

Take a look at the testBasicExample() method in ExampleTest.php.

The testBasicExample() method

  public function testBasicExample()
  {
    $this->visit('/')
         ->see('Laravel 5');
  }

This test says “Visit the home page and we should see the words ‘Laravel 5’.” Can tests get any simpler than this?

The TestCase class provides additional Laravel 5.1 specific application methods and properties to your unit tests. TestCase also provides a long list of additional assertion methods and crawler type tests.

Laravel 5.1 Crawler Methods and Properties

The Crawler tests allow you to test pages in your web application. The nice thing is that many of these tests are fluent and return $this, allowing you to build the ->visit()->see() type test in the above example.

Here are some of the available properties and methods.

$this->response
The last response returned by the web application.
$this->currentUri
The current URL being viewed.
visit($uri)
(Fluent) Visit the given URI with a GET request.
get($uri, array $headers = [])
(Fluent) Fetch the given URI with a GET request, optionally passing headers.
post($uri, array $data = [], array $headers = [])
(Fluent) Make a POST request to the specified URI.
put($uri, array $data = [], array $headers = [])
(Fluent) Make a PUT request to the specified URI.
patch($uri, array $data = [], array $headers = [])
(Fluent) Make a PATCH request to the specified URI.
delete($uri, array $data = [], array $headers = [])
(Fluent) Make a DELETE request to the specified URI.
followRedirects()
(Fluent) Follow any redirects from latest response.
see($text, $negate = false)
(Fluent) Assert the given text appears (or doesn’t appear) on the page.
seeJson(array $data = null)
(Fluent) Assert the response contains JSON. If $data passed, also asserts the JSON value exactly matches.
seeStatusCode($status)
(Fluent) Assert the response has the expected status code.
seePageIs($uri)
(Fluent) Assert current page matches given URI.
seeOnPage($uri) and landOn($uri)
(Fluent) Aliases to seePageIs()
click($name)
(Fluent) Click on a link with the given body, name or id.
type($text, $element)
(Fluent) Fill an input field with the given text.
check($element)
(Fluent) Check a checkbox on the page.
select($option, $element)
(Fluent) Select an option from a dropdown.
attach($absolutePath, $element)
(Fluent) Attach a file to a form field.
press($buttonText)
(Fluent) Submit a form using the button with the given text.
withoutMiddleware()
(Fluent) Disable middleware for the test.
dump()
Dump the content of the latest response.

Laravel 5.1 PHPUnit Application methods and properties

Here’s a brief rundown of some of the additional application methods and properties Laravel 5.1 provides to PHPUnit.

$app
The instance of the Laravel 5.1 application.
$code
The latest code returned by artisan
refreshApplication()
Refreshes the application. Automatically called by the TestCase’s setup() method.
call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
Calls the given URI and returns the response.
callSecure($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
Calls the given HTTPS URI and returns the response.
action($method, $action, $wildcards = [], $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
Calls a controller action and returns the response.
route($method, $name, $routeParameters = [], $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
Calls a named route and returns the response.
instance($abstract, $object)
Register an instance of an object in the container.
expectsEvents($events)
Specify a list of events that should be fired for the given operation.
withoutEvents()
Mock the event dispatcher so all events are silenced.
expectsJobs($jobs)
Specify a list of jobs that should be dispatched for the given operation.
withSession(array $data)
Set the session to the given array.
session(array $data)
Starts session and sets the session values from the array.
flushSession()
Flushes the contents of the current session.
startSession()
Starts the application’s session.
actingAs($user)
(Fluent) Sets the currently logged in user for the application.
be($user)
Sets the currently logged in user for the application.
seeInDatabase($table, array $data, $connection = null)
(Fluent) Asserts a given where condition exists in the database.
notSeeInDatabase($table, $array $data, $connection = null)
(Fluent) Asserts a given where condition does not exist in the database.
missingFromDatabase($table, array $data, $connection = null)
(Fluent) Alias to notSeeInDatabase().
seed()
Seeds the database.
artisan($command, $parameters = [])
Executes the artisan command and returns the code.

Any of these methods or properties can be accessed within your test classes. The provided ExampleTest.php file contains a line using $this->call(...) inside the testBasicExample() method.

Laravel 5.1 PHPUnit Assertions

In addition to the standard PHPUnit assertions (such as assertEquals(), assertContains(), assertInstanceOf(), …), Laravel 5.1 provides many additional assertions to help write tests dealing with the web application.

assertPageLoaded($uri, $message = null)
Assert the latest page loaded; throw exception with $uri/$message if not.
assertResponseOk()
Assert that the client response has an OK status code.
assertReponseStatus($code)
Assert that the client response has a given code.
assertViewHas($key, $value = null)
Assert that the response view has a given piece of bound data.
assertViewHasAll($bindings)
Assert that the view has a given list of bound data.
assertViewMissing($key)
Assert that the response view is missing a piece of bound data.
assertRedirectedTo($uri, $with = [])
Assert whether the client was redirected to a given URI.
assertRedirectedToRoute($name, $parameters = [], $with = [])
Assert whether the client was redirected to a given route.
assertRedirectedToAction($name, $parameters = [], $with = [])
Assert whether the client was redirected to a given action.
assertSessionHas($key, $value = null)
Assert that the session has given key(s)/value(s).
assertSessionHasAll($bindings)
Assert that the session has a given list of values.
assertSessionHasErrors($bindings = [])
Assert that the session has errors bound.
assertHasOldInput()
Assert that the session has old input.

Using Gulp for TDD

Gulp is a build and automation system written in Javascript. It allows common tasks such as minification of source files to be automated. Gulp can even watch your source code for changes and automatically run tasks when this occurs.

Laravel 5.1 includes Laravel Elixir which allows Gulp tasks to be built in easy ways. Elixir adds an elegant syntax to gulp. Think of it this way … what Laravel is to PHP, Elixir is to Gulp.

One of the most common uses of Gulp is to automate unit tests. We’ll follow the TDD (Test Driven Development) process here and let Gulp automatically run our tests.

First, edit the gulpfile.js file in the l5beauty project’s root directory to match what’s below.

Configuring Gulp to run PHPUnit Tests

var elixir = require('laravel-elixir');

elixir(function(mix) {
    mix.phpUnit();
});

Here we call the elixir() function, passing a function. The mix object this function will receive will be a stream on which multiple things can happen. You might want to build LESS files into CSS files here, then concatenate those CSS files together, and then provide versioning on the resulting concatenated files. All of those things can be specified by using a fluent interface on the mix object.

But for now, we’re only running PHPUnit tests.

Next, from the project root on your Host OS, run gulp to see what happens.

Running Gulp

~% cd Code/l5beauty
~/Code/l5beauty% gulp
[15:26:23] Using gulpfile ~/Code/l5beauty/gulpfile.js
[15:26:23] Starting 'default'...
[15:26:23] Starting 'phpunit'...
[15:26:25] Finished 'default' after 2.15 s
[15:26:25]

       *** Debug Cmd: ./vendor/bin/phpunit --colors --debug ***

[15:26:28] PHPUnit 4.7.4 by Sebastian Bergmann and contributors.

Configuration read from /Users/chuck/Code/l5beauty/phpunit.xml


Starting test 'ExampleTest::testBasicExample'.
.

Time: 2.07 seconds, Memory: 10.25Mb

OK (1 test, 2 assertions)
[15:26:28] gulp-notify: [Green!]
[15:26:28] Finished 'phpunit' after 4.96 s

You should have received a notification, a popup alert of some sort, on your Host OS. The notification should be green which indicates everything tested successfully.

Figure 6.2 - Gulp’s PHPUnit Success on Windows 8.1

PHPUnit Success

To have gulp go into automatic mode for unit tests, use the gulp tdd command in your Host OS.

Running Gulp

~% cd Code/l5beauty
~/Code/l5beauty% gulp tdd
[15:29:49] Using gulpfile ~/Code/l5beauty/gulpfile.js
[15:29:49] Starting 'tdd'...
[15:29:49] Finished 'tdd' after 21 ms

The command will just hang there, watching for source file changes and running unit tests when needed.

To see how this works, let’s break the existing unit test.

Change the see() line in tests/ExampleTest.php to what’s below.

Breaking ExampleTest.php

  ->see('Laravel 5x');

When you save this file, gulp will notice and run PHPUnit again. The will fail and you will see a notice on your computer similar to the one below.

Figure 6.3 - Gulp’s PHPUnit Failure on Mac

PHPUnit Failure

Change the line back to what it was before, save it, and again gulp will run PHPUnit. This time you should receive a notice indicating you are “back to green”.

To exit Gulp’s tdd mode

Simply press Ctrl+C

Creating a Markdown Service

The blogging application we’ll be building will allow editing posts in Markdown format. If you aren’t familiar with Markdown, check out the link. It’s an easy-to-read and easy-to-write format that transforms easily to HTML.

To illustrate testing, we’ll build a service to convert markdown text to HTML text using TDD.

Pulling in Markdown Packages

There are many PHP packages out there for converting Markdown to HTML. If you go to http://packagist.org and search for markdown there are twenty pages of packages.

We’ll use the package created by Michel Fortin because there’s another package called SmartyPants by the same author that converts quotation marks to the nice looking curly quotes.

From your Host OS’s console do the following to pull in the packages.

Adding Markdown and SmartyPants

~/Code/l5beauty% composer require michelf/php-markdown
Using version ^1.5 for michelf/php-markdown
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing michelf/php-markdown (1.5.0)
    Downloading: 100%

Writing lock file
Generating autoload files
Generating optimized class loader

~/Code/l5beauty% composer require "michelf/php-smartypants=1.6.0-beta1"
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing michelf/php-smartypants (1.6.0-beta1)
    Loading from cache

Writing lock file
Generating autoload files
Generating optimized class loader

Did you notice that the specific version of the package was specified when requiring SmartyPants? This is because at the time of this writing there isn’t a stable package that can be pulled in automatically.

Creating the Markdown Test Class

The first thing to do when starting a TDD session is to fire up Gulp in TDD mode.

Starting Gulp in TDD mode

~/Code/l5beauty% gulp tdd
[19:41:38] Using gulpfile ~/Code/l5beauty/gulpfile.js
[19:41:38] Starting 'tdd'...
[19:41:38] Finished 'tdd' after 23 ms

Now that Gulp is watching for changes and ready to run PHPUnit as soon as it detects any, let’s create the test class.

In the tests directory, create a new folder named Services and a file called MarkdownerTest.php.

Initial tests/Services/MarkdownerTest.php

<?php

class MarkdownerTest extends TestCase
{

  protected $markdown;

  public function setup()
  {
    $this->markdown = new \App\Services\Markdowner();
  }

  public function testSimpleParagraph()
  {
    $this->assertEquals(
      "<p>test</p>\n",
      $this->markdown->toHTML('test')
    );
  }
}

Line 6
Store an instance of the markdown object
Line 8
Have the setup() method create a new instance of the Markdowner class. (Yes, this doesn’t exist yet.)
Line 13
A simple test we know should work.

You should have received a failure notice. (If you didn’t Ctrl+C out of Gulp and restart it.)

Even though a notice appeared saying the test failed, sometimes it’s useful to look at the console to determine what the failure was. In this case, it’s pretty obvious. The App\Services\Markdowner class doesn’t exist.

Creating the Markdowner Service

What we’ll do here is create a simple service that wraps the php-markdown and php-smartypants packages we imported earlier.

In the app\Services directory create a Markdowner.php file with the following contents.

Contents of app/Services/Markdowner.

<?php

namespace App\Services;

use Michelf\MarkdownExtra;
use Michelf\SmartyPants;

class Markdowner
{

  public function toHTML($text)
  {
    $text = $this->preTransformText($text);
    $text = MarkdownExtra::defaultTransform($text);
    $text = SmartyPants::defaultTransform($text);
    $text = $this->postTransformText($text);
    return $text;
  }

  protected function preTransformText($text)
  {
    return $text;
  }

  protected function postTransformText($text)
  {
    return $text;
  }
}

Line 3
Don’t forget the namespace.
Lines 5 and 6
The classes we’ll be using.
Line 11
The toHTML() method which runs the text through the transformations.
Line 14
Notice we’re using the Markdown Extra version of the library.
Line 20
In case we want to later do our own transformations before anything else.
Line 25
Like preTransformText(), but this time if we later want to add our own final transformations.

When you save this file, Gulp should notice and you will receive a “GREEN” alert telling you everything worked as expected.

If you don’t receive the green alert, go back and check for typos in both the App\Services\Markdowner and MarkdownerTest classes.

A Few More Tests

Admittedly, this isn’t a great example of TDD because it’s simple a test and a complete class created to fix the test. In actual practice TDD would have many more iterations, resulting in a flow like the one below:

  • Create MarkdownerTest w/ testSimpleParagraph()
  • Tests Fail
  • Create Markdowner class, hardcoding toHTML() to pass the test
  • Tests Succeed
  • Update Markdowner class to use MarkdownExtra
  • Tests Succeed
  • Add a testQuotes() to MarkdownerTest class
  • Tests Fail
  • Update Markdowner class to use SmartyPants
  • Tests Succeed

And so forth. Even the structure of our Markdowner class is flawed when it comes to testing. To do pure unit testing on this class it should be structured such that instances of both the MarkdownExtra and SmartyPants classes are injected into the constructor. This way our unit test could inject mock objects and only verify the behavior of MarkdownExtra and not the classes it calls.

But this isn’t a book on testing. In fact, this is the only chapter where testing is discussed. We’ll leave the structure as is but add just a couple more tests.

Update MarkdownerTest to match what’s below.

Final Contents of app/Services/Markdowner.

<?php

class MarkdownerTest extends TestCase
{

  protected $markdown;

  public function setup()
  {
    $this->markdown = new \App\Services\Markdowner();
  }

  /**
   * @dataProvider conversionsProvider
   */
  public function testConversions($value, $expected)
  {
    $this->assertEquals($expected, $this->markdown->toHTML($value));
  }

  public function conversionsProvider()
  {
    return [
      ["test", "<p>test</p>\n"],
      ["# title", "<h1>title</h1>\n"],
      ["Here's Johnny!", "<p>Here&#8217;s Johnny!</p>\n"],
    ];
  }
}

Here we changed the test class to test multiple conversions at once and added three tests in conversionsProvider(). Your tests should be green before moving forward.

Once the tests are green hit Ctrl+C in your Host OS console to stop Gulp.

Other Ways to Test

It’s not the intent here to provide a definitive list of all the ways to test with Laravel 5.1 because there’s really no single way to do testing in PHP. Therefore, there’s no single way to test in Laravel 5.

But, we’ll explore some alternatives.

phpspec

Besides PHPUnit, Laravel 5.1 also provides phpspec out of the box. This is another popular PHP test suit with more of a focus on Behavior Driven Development.

Here’s a few notes on phpspec.

  • The binary is in vendor/bin, thus you can call phpspec from your project’s root directory.
  • The configuration file is in the project root. It’s named phpspec.yml.
  • To run phpspec from Gulp, Elixir provides the phpSpec() function you can call on the mix object.
  • If you change your application’s namespace from App to something else, be sure to update phpspec.yml accordingly.

Unit Testing

Although PHPUnit is the standard when it comes to PHP unit testing, there are other packages you can use.

  • Enhance PHP - A unit testing framework with support for mocks and stubs.
  • SimpleTest - Another unit testing framework with mock objects.

Functional / Acceptance Testing

These tests actually use your application instead of just verifying that units of code within your application work. When using the fluent test methods Laravel 5.1 provides you can actually do some functional tests using PHPUnit. ExampleTest.php shows a simple example. But there are other testing frameworks that focus on functional / acceptance testing.

  • Codeception - Problem the most popular framework for acceptance testing.
  • Selenium - Browser automation.
  • Mink - Brower automation.

Behavior Driven Development

BDD comes in two flavors: SpecBDD and StoryBDD.

SpecDD focuses on the technical aspects of your code. Laravel 5.1 includes phpspec which is the standard for SpecDD.

StoryBDD emphasizes business or feature testing. Behat is the most popular StoryBDD framework. Although, Codeception can also be used for StoryBDD.

Recap

The first thing we did in this chapter was creating a project named l5beauty. Then we explored unit testing using PHPUnit within this project. Finally, we created a Markdowner service class for the dual purposes of having something to test and to use later to convert markdown text to HTML.

This was a pretty long chapter because testing is a large topic and a single chapter cannot give it justice. But, as I’ve mentioned, testing is not the focus of this book. There will be no more testing in subsequent chapters.

How about something quicker for the next chapter?

comments powered by Disqus