Laravel - PHPUnit

laravel

https://www.youtube.com/watch?v=-9YVcssCACI
https://www.youtube.com/watch?v=84j61_aI0q8
https://www.youtube.com/watch?v=4BXpi7056RM

How is Laravel integrate with PHPUnit?

Laravel is built with testing in mind. In fact, support for testing with PHPUnit is included out of the box and a phpunit.xml file is already setup for your application. The framework also ships with convenient helper methods that allow you to expressively test your applications. An ExampleTest.php file is provided in the tests directory. After installing a new Laravel application, simply run phpunit on the command line to run your tests.

When running tests, Laravel will automatically set the configuration environment to testing. Laravel automatically configures the session and cache to the array driver while testing, meaning no session or cache data will be persisted while testing. You are free to define other testing environment configuration values as necessary. The testing environment variables may be configured in the phpunit.xml file, but make sure to clear your configuration cache using the config:clear Artisan command before running your tests!

How can we we install PHPUnit via Composer?

composer require "phpunit/phpunit"

The above command install phpunit under the vendor/bin folder.

How can we create a new test case?

Use the make:test Artisan command:

php artisan make:test UserTest

This command will place a new UserTest class within your tests directory. You may then define test methods as you normally would using PHPUnit. To run your tests, simply execute the phpunit command from your terminal:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class UserTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testExample()
    {
        $this->assertTrue(true);
    }
}

If you define your own setUp method within a test class, be sure to call parent::setUp.

Sample unit test:

<?php
require_once 'person.php';
class PersonTest extends PHPUnit_Framework_TestCase {

  private $person;

  public function setUp() {
    $person = new Person("Jason");
  }

  public function testName() {
    $name = $person->getName();
    $this->assertEquals("Jason", $person->getName();
  }
}

What are the status code produced by phpunit?

  1. . : the test succeeds.
  2. F : an assertion fails while running the test method.
  3. E : error occurs while running the test method.
  4. R : the test has been marked as risky
  5. S : the test has been skipped
  6. I : the test is marked as being incomplete or not yet implemented

How can we run phpunit on a single test cass class?

phpunit tests/CurrencyTest

The above command runs only the tests that are declared in the CurrencyTest test case class in tests/CurrencyTest.php

How can we create a test suite?

Add to the phpunit.xml:

<testsuites>
    <testsuite name="Application Test Suite">
        <directory suffix="Test.php">./tests</directory>
    </testsuite>
</testsuites>

The order in which tests are executed can be made explicit:

<testsuites>
  <testsuite name="money">
    <file>tests/IntlFormatterTest.php</file>
    <file>tests/MoneyTest.php</file>
    <file>tests/CurrencyTest.php</file>
  </testsuite>
</testsuites>

How can we mark a test as dependent on another test?

Use the @depends annotation:

<?php
use PHPUnit\Framework\TestCase;

class StackTest extends TestCase
{
    public function testEmpty()
    {
        $stack = [];
        $this->assertEmpty($stack);

        return $stack;
    }

    /**
     * @depends testEmpty
     */
    public function testPush(array $stack)
    {
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertNotEmpty($stack);

        return $stack;
    }

    /**
     * @depends testPush
     */
    public function testPop(array $stack)
    {
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEmpty($stack);
    }
}
?>

How can we specify data provider for a test?

Use the @dataProvider annotation:

<?php
use PHPUnit\Framework\TestCase;

class DataTest extends TestCase
{
    /**
     * @dataProvider additionProvider
     */
    public function testAdd($a, $b, $expected)
    {
        $this->assertEquals($expected, $a + $b);
    }

    public function additionProvider()
    {
        return [
            [0, 0, 0],
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 3]
        ];
    }
}
?>

A test method can accept arbitrary arguments. These arguments are to be provided by a data provider method. The data provider method to be used is specified using the @dataProvider annotation.

A data provider method must be public and either return an array of arrays or an object that implements the Iterator interface and yields an array for each iteration step. For each array that is part of the collection the test method will be called with the contents of the array as its arguments.

When using a large number of datasets it's useful to name each one with string key instead of default numeric. Output will be more verbose as it'll contain that name of a dataset that breaks a test.

<?php
use PHPUnit\Framework\TestCase;

class DataTest extends TestCase
{
    /**
     * @dataProvider additionProvider
     */
    public function testAdd($a, $b, $expected)
    {
        $this->assertEquals($expected, $a + $b);
    }

    public function additionProvider()
    {
        return [
            'adding zeros'  => [0, 0, 0],
            'zero plus one' => [0, 1, 1],
            'one plus zero' => [1, 0, 1],
            'one plus one'  => [1, 1, 3]
        ];
    }
}
?>

Using a data provider that returns an Iterator object:

<?php
use PHPUnit\Framework\TestCase;

require 'CsvFileIterator.php';

class DataTest extends TestCase
{
    /**
     * @dataProvider additionProvider
     */
    public function testAdd($a, $b, $expected)
    {
        $this->assertEquals($expected, $a + $b);
    }

    public function additionProvider()
    {
        return new CsvFileIterator('data.csv');
    }
}
?>

CsvFileIterator class:

<?php
use PHPUnit\Framework\TestCase;

class CsvFileIterator implements Iterator {
    protected $file;
    protected $key = 0;
    protected $current;

    public function __construct($file) {
        $this->file = fopen($file, 'r');
    }

    public function __destruct() {
        fclose($this->file);
    }

    public function rewind() {
        rewind($this->file);
        $this->current = fgetcsv($this->file);
        $this->key = 0;
    }

    public function valid() {
        return !feof($this->file);
    }

    public function key() {
        return $this->key;
    }

    public function current() {
        return $this->current;
    }

    public function next() {
        $this->current = fgetcsv($this->file);
        $this->key++;
    }
}
?>

How can we test for exceptions?

Use expectException:

<?php
use PHPUnit\Framework\TestCase;

class ExceptionTest extends TestCase
{
    public function testException()
    {
        $this->expectException(InvalidArgumentException::class);
    }
}
?>

In addition to the expectException() method the expectExceptionCode(), expectExceptionMessage(), and expectExceptionMessageRegExp() methods exist to set up expectations for exceptions raised by the code under test. Alternatively, you can use the @expectedException, @expectedExceptionCode, @expectedExceptionMessage, and @expectedExceptionMessageRegExp annotations to set up expectations for exceptions raised by the code under test.

<?php
use PHPUnit\Framework\TestCase;

class ExceptionTest extends TestCase
{
    /**
     * @expectedException InvalidArgumentException
     */
    public function testException()
    {
    }
}
?>

How can we test for errors?

By default, PHPUnit converts PHP errors, warnings, and notices that are triggered during the execution of a test to an exception. Using these exceptions, you can, for instance, expect a test to trigger a PHP error:

<?php
use PHPUnit\Framework\TestCase;

class ExpectedErrorTest extends TestCase
{
    /**
     * @expectedException PHPUnit_Framework_Error
     */
    public function testFailingInclude()
    {
        include 'not_existing_file.php';
    }
}
?>
phpunit -d error_reporting=2 ExpectedErrorTest

PHP's error_reporting runtime configuration can limit which errors PHPUnit will convert to exceptions. If you are having issues with this feature, be sure PHP is not configured to suppress the type of errors you're testing.

PHPUnit_Framework_Error_Notice and PHPUnit_Framework_Error_Warning represent PHP notices and warnings, respectively.

You should be as specific as possible when testing exceptions. Testing for classes that are too generic might lead to undesirable side-effects. Accordingly, testing for the Exception class with @expectedException or setExpectedException() is no longer permitted.

When testing that relies on php functions that trigger errors like fopen it can sometimes be useful to use error suppression while testing. This allows you to check the return values by suppressing notices that would lead to a phpunit PHPUnit_Framework_Error_Notice.

Testing return values of code that uses PHP Errors:

<?php
use PHPUnit\Framework\TestCase;

class ErrorSuppressionTest extends TestCase
{
    public function testFileWriting() {
        $writer = new FileWriter;
        $this->assertFalse(@$writer->write('/is-not-writeable/file', 'stuff'));
    }
}
class FileWriter
{
    public function write($file, $content) {
        $file = fopen($file, 'w');
        if($file == false) {
            return false;
        }
        // ...
    }
}

?>

How can we test for output?

Use expectOutputString, expectOutputRegex, setOutputCallback

Sometimes you want to assert that the execution of a method, for instance, generates an expected output (via echo or print, for example). The PHPUnit\Framework\TestCase class uses PHP's Output Buffering feature to provide the functionality that is necessary for this.

<?php
use PHPUnit\Framework\TestCase;

class OutputTest extends TestCase
{
    public function testExpectFooActualFoo()
    {
        $this->expectOutputString('foo');
        print 'foo';
    }

    public function testExpectBarActualBaz()
    {
        $this->expectOutputString('bar');
        print 'baz';
    }
}
?>

How can we test database interaction using PHPUnit?

There is a lot of challenges with testing database. However, DbUnit helps to simplify all these problems with database testing in an elegant way. What PHPUnit cannot help you with is the fact that database tests are very slow compared to tests not using the database. Depending on how large the interactions with your database are your tests could run a considerable amount of time. However, if you keep the amount of data used for each test small and try to test as much code using non-database tests you can easily get away in under a minute even for large test suites.

The four stages of a database test:

  1. Set up fixture
  2. Exercise System Under Test
  3. Verify outcome
  4. Teardown

A fixture describes the initial state your application and database are in when you execute a test. PHPUnit will execute a TRUNCATE against all the tables you specified to reset their status to empty. PHPUnit will then iterate over all the fixture rows specified and insert them into their respective tables. After the database is reset and loaded with its initial state the actual test is executed by PHPUnit. This part of the test code does not require awareness of the Database Extension at all, you can go on and test whatever you like with your code.

In your test use a special assertion called assertDataSetsEqual() for verification purposes, however, this is entirely optional. This feature will be explained in the section “Database Assertions”.

class MyGuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    /**
     * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
     */
    public function getConnection()
    {
        $pdo = new PDO('sqlite::memory:');
        return $this->createDefaultDBConnection($pdo, ':memory:');
    }

    /**
     * @return PHPUnit_Extensions_Database_DataSet_IDataSet
     */
    public function getDataSet()
    {
        return $this->createFlatXMLDataSet(dirname(__FILE__).'/_files/guestbook-seed.xml');
    }
}

To allow the clean-up and fixture loading functionalities to work the PHPUnit Database Extension requires access to a database connection abstracted across vendors through the PDO library. It is important to note that your application does not need to be based on PDO to use PHPUnit's database extension, the connection is merely used for the clean-up and fixture setup.

In the previous example we create an in-memory Sqlite connection and pass it to the createDefaultDBConnection method which wraps the PDO instance and the second parameter (the database-name) in a very simple abstraction layer for database connections of the type PHPUnit_Extensions_Database_DB_IDatabaseConnection.

The getDataSet() method defines how the initial state of the database should look before each test is executed. The state of a database is abstracted through the concepts DataSet and DataTable both being represented by the interfaces PHPUnit_Extensions_Database_DataSet_IDataSet and PHPUnit_Extensions_Database_DataSet_IDataTable. The next section will describe in detail how these concepts work and what the benefits are for using them in database testing.

For the implementation we only need to know that the getDataSet() method is called once during setUp() to retrieve the fixture data-set and insert it into the database. In the example we are using a factory method createFlatXMLDataSet($filename) that represents a data-set through an XML representation.

PHPUnit assumes that the database schema with all its tables, triggers, sequences and views is created before a test is run. This means you as developer have to make sure that the database is correctly setup before running the suite.

If you are using a persistent database (not Sqlite Memory) you can easily setup the database once with tools such as phpMyAdmin for MySQL and re-use the database for every test-run. If you are using libraries such as Doctrine 2 or Propel you can use their APIs to create the database schema you need once before you run the tests. You can utilize PHPUnit's Bootstrap and Configuration capabilities to execute this code whenever your tests are run.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License