Laravel Package Testing with Testbench Component (orchestral/testbench)

Good Systrm

When developing with Laravel, in some cases we just write everything around a default installation, to get features done as quickly as possible. In that case, everything needed for running PHPUnit is already set up after running

composer create-project laravel/laravel=5.x PROJECT-FOLDER

We can start writing and running tests immediately after installation is done.

In some other cases, we want to take time to put everything in a package of its own, and conveniently reuse them later. To test out this package, we could install it in a default Laravel installation, and test it as such. Alternatively, we could use Testbench Component (orchestral/testbench) to test it in isolation.

Instructions on the Github page of Testbench Component is pretty clear and examples are solid. However, for developers who are less familiar with PHPUnit testing, it may take not-so-insignificant amount of time to figure out how to set up the the package before a bare minimal test can run and pass.

So the focus here is the basic setup to get tests running. And one example is provided to show what could be different between testing a package isolated, and testing it within the context of an installed Laravel instance. The test package can be found at https://github.com/good-system/test-laravel-package-isolated.

Create a bare minimal package from scratch

First create a project folder, say test-laravel-package-isolated, then enter the folder and run

composer init

We’ll need to answer a bunch of questions, some of which are not required. Something like this

Welcome to the Composer config generatorThis command will guide you through creating your composer.json config.Package name (<vendor>/<name>) [hguo/test-laravel-package-isolated]: good-system/test-laravel-package-isolated
Description []:
Author [Good System <real.good.system@gmail.com>, n to skip]: Good System <real.good.system@gmail.com>
....
Define your dependencies.
Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? yes
Search for a package: testbench
Found 15 packages matching testbench
[0] orchestra/testbench
...
Enter package # to add, or the complete package name if it is not listed: 0
...
Search for a package: phpunit
Found 15 packages matching phpunit
[0] phpunit/phpunit
...
Enter package # to add, or the complete package name if it is not listed: 0
...
Do you confirm generation [yes]?

At the end, we have a bare minimal package with a single file composer.json in it.

{
"name": "good-system/test-laravel-package-isolated",
"require-dev": {
"orchestra/testbench": "^3.7",
"phpunit/phpunit": "^7.5"
},
"authors": [ ... ],
"require": {}
}

Since we chose to install dependencies during this process, now we have all required packages installed under vendor folder too. This can be installed and used in any PHP project, including a Laravel project.

Make it a Laravel Package

To use it in a Laravel project, we also need a ServiceProvider class. Create one now

<?php
// test-laravel-package-isolated/src/TestPackageServiceProvider.php
namespace GoodSystem\TestPackage;use Illuminate\Support\ServiceProvider;
class TestPackageServiceProvider extends ServiceProvider
{
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/routes/Test.php');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'test');
}
// ....
}

Also, we want a simple route that uses a view:

<?php
// test-laravel-package-isolated/src/routes/Test.php
Route::get('test', function () {
return view('test::default');
});

And the view, in which we want to include a login page link by referencing it using route(). This could be a common scenario if we want to develop a package that relies on Laravel built-in security. We’ll come back later to how that could require additional set up for testing to work.

// test-laravel-package-isolated/resources/views/default.blade.php<html>
<head></head>
<body>
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
Test Laravel package isolated
</body>
</html>

Last Piece Needed for PHPUnit

If we run vendor/bin/phpunit under project root now, it won’t work. We need another piece, phpunit.xml under project root. Again, a bare minimal one (note that we need an APP_KEY for testing to work, and a made-up one would work just fine)

<?xml version="1.0" encoding="UTF-8"?><phpunit 
backupGlobals="false" backupStaticAttributes="false"
colors="true" convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false" stopOnFailure="false"
>
<php>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
</php>
<testsuites>
<testsuite name="Library Program Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

Now Test Route “/test”

Create a test file like this

<?php
// test-laravel-package-isolated/tests/RouteTest.php
use GoodSystem\TestPackage\TestPackageServiceProvider;// When testing inside of a Laravel installation, the base class would be Tests\TestCase
class RouteTest extends Orchestra\Testbench\TestCase
{
// Use annotation @test so that PHPUnit knows about the test
/** @test */
public function visit_test_route()
{
// Visit /test and see "Test Laravel package isolated" on it
$response = $this->get('test');
$response->assertStatus(200);
$response->assertSee('Test Laravel package isolated');
}
// When testing inside of a Laravel installation, this is not needed
protected function getPackageProviders($app)
{
return [
'GoodSystem\TestPackage\TestPackageServiceProvider'
];
}
// When testing inside of a Laravel installation, this is not needed
protected function setUp()
{
parent::setUp();
}
}

Would The Test Run Tough ?

If we run vendor/bin/phpunit now, we get an error stating

Class ‘GoodSystem\TestPackage\TestPackageServiceProvider’ not found

We need to make composer know about the Service Provider, add the following to package’s composer.json

{
...
"autoload": {
"psr-4": {
"GoodSystem\\TestPackage\\": "src"
}
},
...
}

Then run

composer dump-autoload

Now run the test again, we’ll see a different error

There was 1 failure:
1) RouteTest::visit_test_route
Expected status code 200 but received 500.
Failed asserting that false is true.

Now remember the route “login” we added as a link. Yes, that route doesn’t exist. This is one of the things that could be overlooked. We are so used to it. When we run “php artisan make:auth”, it’s a given when working inside of a Laravel installation. Now we need to make it available when setting up the test

<?php
// test-laravel-package-isolated/tests/RouteTest.php
// ...
class RouteTest extends Orchestra\Testbench\TestCase
{
// ...
// When testing inside of a Laravel installation, this is not needed
protected function setUp()
{
parent::setUp();
// Need this to put Laravel built-in authentication routes in place for testing package isolated
Route::auth();
}
}

Run vendor/bin/phpunit again, now we are GREEN. Phew ^_^

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade