Skip to content

Testing

It is essential to have proper test coverage for the package's provided code. Adding tests to our package can confirm the existing code's behavior, verify everything still works whenever adding new functionality, and ensure we can safely refactor our package with confidence at a later stage.

Additionally, having good code coverage can motivate potential contributors by giving them more confidence that their addition does not break something else in the package. Tests also allow other developers to understand how specific features of your package are to be used and give them confidence about your package's reliability.

Installing PHPUnit

There are many options to test behavior in PHP. However, we'll stay close to Laravel's defaults, which uses the excellent tool PHPUnit.

Install PHPUnit as a dev-dependency in our package:

composer require --dev phpunit/phpunit

Note: you might need to install a specific version if you're developing a package for an older version of Laravel. Also to install orchestra/testbench, please refer to Orchestra Testbench set up on the Development Environment page.

To configure PHPUnit, create a phpunit.xml file in the root directory of the package. Then, copy the following template to use an in-memory sqlite database and enable colorful reporting.

phpunit.xml:

phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     bootstrap="vendor/autoload.php"
     backupGlobals="false"
     colors="true"
     processIsolation="false"
     stopOnFailure="false"
     xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"
     cacheDirectory=".phpunit.cache"
     backupStaticProperties="false"
>
  <coverage/>
  <testsuites>
    <testsuite name="Unit">
      <directory suffix="Test.php">./tests/Unit</directory>
    </testsuite>
    <testsuite name="Feature">
      <directory suffix="Test.php">./tests/Feature</directory>
    </testsuite>
  </testsuites>
  <php>
    <env name="DB_CONNECTION" value="testing"/>
    <env name="APP_KEY" value="base64:2fl+Ktvkfl+Fuz4Qp/A75G2RTiWVA/ZoKZvp6fiiM10="/>
  </php>
  <source>
    <include>
      <directory suffix=".php">src/</directory>
    </include>
  </source>
</phpunit>

Note the dummy APP_KEY in the example above. This environment variable is consumed by Laravel's encrypter, which your tests might be making use of. For most cases, the dummy value will be sufficient. However, you are free to either change this value to reflect an actual app key (of your Laravel application) or leave it off entirely if your test suite does not interact with the encrypter.

Directory Structure

To accommodate Feature and Unit tests, create a tests/ directory with a Unit and Feature subdirectory and a base TestCase.php file. The structure looks as follows:

- tests
    - Feature
    - Unit
      TestCase.php

The TestCase.php extends \Orchestra\Testbench\TestCase (see example below) and contains tasks related to setting up our “world” before each test is executed. In the TestCase class we will implement three important set-up methods: setUp(), getEnvironmentSetUp() and getPackageProviders().

Let's look at these methods one by one:

  • setUp(): You might have already used this method in your tests. Often it is used when you need a certain model in all following tests. The instantiation of that model can therefore be extracted to a setUp() method which is called before each test. Within the tests, the desired model can be retrieved from the Test class instance variable. When using this method, don't forget to call the parent setUp() method (and make sure to return void).

  • getEnvironmentSetUp(): As suggested by Orchestra Testbench: "If you need to add something early in the application bootstrapping process, you could use the getEnvironmentSetUp() method". Therefore, I suggest it is called before the setUp() method(s).

  • getPackageProviders(): As the name suggests, we can load our service provider(s) within the getPackageProviders() method. We'll do that by returning an array containing all providers. For now, we'll just include the package specific package provider, but imagine that if the package uses an EventServiceProvider, we would also register it here.


In a package, TestCase will inherit from the Orchestra Testbench TestCase:

// 'tests/TestCase.php'
<?php

namespace JohnDoe\BlogPackage\Tests;

use JohnDoe\BlogPackage\BlogPackageServiceProvider;

class TestCase extends \Orchestra\Testbench\TestCase
{
  public function setUp(): void
  {
    parent::setUp();
    // additional setup
  }

  protected function getPackageProviders($app)
  {
    return [
      BlogPackageServiceProvider::class,
    ];
  }

  protected function getEnvironmentSetUp($app)
  {
    // perform environment setup
  }
}

Before we can run the PHPUnit test suite, we first need to map our testing namespace to the appropriate folder in the composer.json file under an "autoload-dev" (psr-4) key:

{
  ...,

  "autoload": {},

  "autoload-dev": {
    "psr-4": {
      "JohnDoe\\BlogPackage\\Tests\\": "tests"
    }
  }
}

Finally, re-render the autoload file by running composer dump-autoload.

Authentication

In some cases you might want to use Laravel's User::class to be able to use an authenticated user in your tests. There are several approaches, as discussed in the Models related to App\User section. However, if you don't have any relationships with the User model, and only want to test authentication logic, the easiest option is to create your own User class, extending the Illuminate\Foundation\Auth\User class:

App/Models/User.php
<?php

use Illuminate\Foundation\Auth\User as BaseUser;

class User extends BaseUser
{
    protected $table = 'users';
}
After defining this custom User model within your package, you should execute the migrate command from the Orchestra package to create the users table in your test database:

<?php

$this->loadLaravelMigrations(['--database' => 'testbench']);
$this->artisan('migrate', ['--database' => 'testbench'])->run();
Finally, you can use the package's User::class in your tests within the $this->actingAs() helper and send a request by an authenticated user.