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
- Running PHPUnit
- Using Gulp for TDD
- Creating a Markdown Service
- Other Ways Test
- Recap
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
|
> [email protected] install /Users/chuck/Code/l5beauty/node_modules/laravel- elixir/node_modules/gulp-sass/node_modules/node-sass
> node scripts/install.js
> [email protected] 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
[email protected] node_modules/gulp
├── [email protected]
├── [email protected]
[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
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.
ExampleTest.php
- Contains one testtestBasicExample()
. TheExampleTest
class is derived from theTestCase
parent provided in the other file.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)
andlandOn($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
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
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 theMarkdowner
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’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 callphpspec
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 themix
object. - If you change your application’s namespace from
App
to something else, be sure to updatephpspec.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?