#######
Testing
#######

CodeIgniter has been built to make testing both the framework and your application as simple as possible.
Support for ``PHPUnit`` is built in, and the framework provides a number of convenient 
helper methods to make testing every aspect of your application as painless as possible.

.. contents::
    :local:
    :depth: 2

========================
Testing Your Application
========================

The Test Class
==============

In order to take advantage of the additional tools provided, your tests must extend ``\CIUnitTestCase``::

    class MyTests extends \CIUnitTestCase
    {
        . . .
    }


Mocking Services
================

You will often find that you need to mock one of the services defined in **application/Config/Services.php** to limit
your tests to only the code in question, while simulating various responses from the services. This is especially
true when testing controllers and other integration testing. CodeIgniter makes this simple.

While in test mode, the system loads a wrapper around the **Services** class that provides two new methods,
``injectMock()``, and ``reset()``.

**injectMock()**

This method allows you to define the exact instance that will be returned by the Services class. You can use this to
set properties of a service so that it behaves in a certain way, or replace a service with a mocked class.
::

    public function testSomething()
    {
        $curlrequest = $this->getMockBuilder('CodeIgniter\HTTP\CURLRequest')
                            ->setMethods(['request'])
                            ->getMock();
        Services::injectMock('curlrequest', $curlrequest);

        // Do normal testing here....
    }

The first parameter is the service that you are replacing. The name must match the function name in the Services
class exactly. The second parameter is the instance to replace it with.

**reset()**

Removes all mocked classes from the Services class, bringing it back to its original state.

PHPUnit Configuration
=====================

The framework has a ``phpunit.xml.dist`` file in the project root. This controls unit
testing of the framework itself. If you provide your own ``phpunit.xml``, it will
over-ride this.

Your ``phpunit.xml`` should exclude the ``system`` folder, as well as any ``vendor`` or
``ThirdParty`` folders, if you are unit testing your application.

===================
Testing Controllers
===================

Testing your controllers is made convenient with a couple of new helper classes and traits. When testing controllers,
you can execute the code within a controller, without first running through the entire application bootstrap process.

.. note:: Because the entire framework has not been bootstrapped, there will be times when you cannot test a controller
    this way.

The Helper Trait
================

You can use either of the base test classes described herein, but you do need to use the `ControllerTester` trait
within your tests::

    use Tests\Support\Helpers\ControllerTester;

    class TestControllerA extends CIDatabaseTestCase
    {
        use ControllerTester;
    }

Once the trait has been included, you can start setting up the environment, including the request and response classes,
the request body, URI, and more. You specify the controller to use with the ``controller()`` method, passing in the
fully qualified class name of your controller. Finally, call the ``execute()`` method with the name of the method
to run as the parameter::

    use Tests\Support\Helpers\ControllerTester;

    class TestControllerA extends CIDatabaseTestCase
    {
        use ControllerTester;

        public function testShowCategories()
        {
            $result = $this->withURI('http://example.com/categories')
			    ->controller(\App\Controllers\ForumController::class)
			    ->execute('showCategories');

            $this->assertTrue($result->isOK());
        }
    }

Helper Methods
==============

**controller($class)**

Specifies the class name of the controller to test. The first parameter must be a fully qualified class name
(i.e. include the namespace)::

    $this->controller(\App\Controllers\ForumController::class);

**execute($method)**

Executes the specified method within the controller. The only parameter is the name of the method to run::

    $results = $this->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

This returns a new helper class that provides a number of routines for checking the response itself. See below
for details.

**withConfig($config)**

Allows you to pass in a modified version of **Config\App.php** to test with different settings::

    $config = new Config\App();
    $config->appTimezone = 'America/Chicago';

    $results = $this->withConfig($config)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

If you do not provide one, the application's App config file will be used.

**withRequest($request)**

Allows you to provide an **IncomingRequest** instance tailored to your testing needs::

    $request = new CodeIgniter\HTTP\IncomingRequest(new Config\App(), new URI('http://example.com'));
    $request->setLocale($locale);

    $results = $this->withRequest($request)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

If you do not provide one, a new IncomingRequest instance with the default application values will be passed
into your controller.

**withResponse($response)**

Allows you to provide a **Response** instance::

    $response = new CodeIgniter\HTTP\Response(new Config\App());

    $results = $this->withResponse($response)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

If you do not provide one, a new Response instance with the default application values will be passed
into your controller.

**withURI($uri)**

Allows you to provide a new URI that simulates the URL the client was visiting when this controller was run.
This is helpful if you need to check URI segments within your controller. The only parameter is a string
representing a valid URI::

    $results = $this->withURI('http://example.com/forums/categories')
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

It is a good practice to always provide the URI during testing to avoid surprises.

**withBody($body)**

Allows you to provide a custom body for the request. This can be helpful when testing API controllers where
you need to set a JSON value as the body. The only parameter is a string that represents the body of the request::

    $body = json_encode(['foo' => 'bar']);

    $results = $this->withBody($body)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

Checking the Response
=====================

When the controller is executed, a new **ControllerResponse** instance will be returned that provides a number
of helpful methods, as well as direct access to the Request and Response that were generated.

**isOK()**

This provides a simple check that the response would be considered a "successful" response. This primarily checks that
the HTTP status code is within the 200 or 300 ranges::

    $results = $this->withBody($body)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

    if ($results->isOK())
    {
        . . .
    }

**isRedirect()**

Checks to see if the final response was a redirection of some sort::

    $results = $this->withBody($body)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

    if ($results->isRedirect())
    {
        . . .
    }

**request()**

You can access the Request object that was generated with this method::

    $results = $this->withBody($body)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

    $request = $results->request();

**response()**

This allows you access to the response object that was generated, if any::

    $results = $this->withBody($body)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

    $response = $results->response();

**getBody()**

You can access the body of the response that would have been sent to the client with the **getBody()** method. This could
be generated HTML, or a JSON response, etc.::

    $results = $this->withBody($body)
                     ->controller(\App\Controllers\ForumController::class)
                     ->execute('showCategories');

    $body = $results->getBody();

Response Helper methods
-----------------------

The response you get back contains a number of helper methods to inspect the HTML output within the response. These
are useful for using within assertions in your tests.

The **see()** method checks the text on the page to see if it exists either by itself, or more specifically within
a tag, as specified by type, class, or id::

    // Check that "Hello World" is on the page
    $results->see('Hello World');
    // Check that "Hello World" is within an h1 tag
    $results->see('Hello World', 'h1');
    // Check that "Hello World" is within an element with the "notice" class
    $results->see('Hello World', '.notice');
    // Check that "Hello World" is within an element with id of "title"
    $results->see('Hellow World', '#title');

The **dontSee()** method is the exact opposite::

    // Checks that "Hello World" does NOT exist on the page
    $results->dontSee('Hello World");
    // Checks that "Hellow World" does NOT exist within any h1 tag
    $results->dontSee('Hello World', 'h1');

The **seeElement()** and **dontSeeElement()** are very similar to the previous methods, but do not look at the
values of the elements. Instead, they simply check that the elements exist on the page::

    // Check that an element with class 'notice' exists
    $results->seeElement('.notice');
    // Check that an element with id 'title' exists
    $results->seeElement('#title')
    // Verify that an element with id 'title' does NOT exist
    $results->dontSeeElement('#title');

You can use **seeLink()** to ensure that a link appears on the page with the specified text::

    // Check that a link exists with 'Upgrade Account' as the text::
    $results->seeLink('Upgrade Account');
    // Check that a link exists with 'Upgrade Account' as the text, AND a class of 'upsell'
    $results->seeLink('Upgrade Account', '.upsell');

The **seeInField()** method checks for any input tags exist with the name and value::

    // Check that an input exists named 'user' with the value 'John Snow'
    $results->seeInField('user', 'John Snow');
    // Check a multi-dimensional input
    $results->seeInField('user[name]', 'John Snow');

Finally, you can check if a checkbox exists and is checked with the **seeCheckboxIsChecked()** method::

    // Check if checkbox is checked with class of 'foo'
    $results->seeCheckboxIsChecked('.foo');
    // Check if checkbox with id of 'bar' is checked
    $results->seeCheckboxIsChecked('#bar');

Stream Filters
==============

Some stream filters have been provided as an alternate to these helper methods.

CITestStreamFilter
------------------

This filter captures output and makes it available to you.

An example demonstrating this inside one of your test cases:

            public function setUp()
            {
                    CITestStreamFilter::$buffer = '';
                    $this->stream_filter = stream_filter_append(STDOUT, 'CITestStreamFilter');
            }

            public function tearDown()
            {
                    stream_filter_remove($this->stream_filter);
            }

            public function testSomeOutput()
            {
                    CLI::write('first.');
                    $expected = "first.\n";
                    $this->assertEquals($expected, CITestStreamFilter::$buffer);
            }


Additional Assertions
=====================

``CIUnitTestCase`` provides additional unit testing assertions that you might find useful.

**assertLogged($level, $expectedMessage)**

Ensure that something you expected to be logged actually was.

        $config = new LoggerConfig();
        $logger = new Logger($config);

        ... do something that you expect a log entry from
        $logger->log('error', "That's no moon");

        $this->assertLogged('error', "That's no moon");

**assertEventTriggered($eventName)**

Ensure that an event you excpected to be triggered actually was:

		Events::on('foo', function($arg) use(&$result) {
			$result = $arg;
		});

		Events::trigger('foo', 'bar');

                $this->assertEventTriggered('foo');


=====================
Testing Your Database
=====================

The Test Class
==============

In order to take advantage of the built-in database tools that CodeIgniter provides for testing, your
tests must extend ``\CIDatabaseTestCase``::

    class MyTests extends \CIDatabaseTestCase
    {
        . . .
    }

Because special functionality executed during the ``setUp()`` and ``tearDown()`` phases, you must ensure
that you call the parent's methods if you need to use those methods, otherwise you will lose much
of the functionality described here.
::

    class MyTests extends \CIDatabaseTestCase
    {
        public function setUp()
        {
            parent::setUp();

            // Do something here....
        }

        public function tearDown()
        {
            parent::tearDown();

            // Do something here....
        }
    }

Test Database Setup
===================

When running database tests, you need to provide a database that can be used during testing. Instead of
using the PHPUnit built-in database features, the framework provides tools specific to CodeIgniter. The first
step is to ensure that you have a ``tests`` database group setup in **application/Config/Database.php**.
This specifies a database connection that is only used while running tests, to keep your other data safe.

If you have multiple developers on your team, you will likely want to keep your credentials store in
the **.env** file. To do so, edit the file to ensure the following lines are present, and have the
correct information::

    database.tests.dbdriver = 'MySQLi';
    database.tests.username = 'root';
    database.tests.password = '';
    database.tests.database = '';

Migrations and Seeds
--------------------

When running tests you need to ensure that your database has the correct schema setup, and that
it is in a known state for every test. You can use migrations and seeds to setup your database,
by adding a couple of class properties to your test.
::

    class MyTests extends \CIDatabaseTestCase
    {
        protected $refresh  = true;
        protected $seed     = 'TestSeeder';
        protected $basePath = 'path/to/database/files';
    }

**$refresh**

This boolean value determines whether the database is completely refreshed before every test. If true,
all migrations are rolled back to version 0, then the database is migrated to the latest available migration.

**$seed**

If present and not empty, this specifies the name of a Seed file that is used to populate the database with
test data prior to every test running.

**$basePath**

By default, CodeIgniter will look in **tests/_support/database/migrations** and **tests/_support_database/seeds**
to locate the migrations and seeds that it should run during testing. You can change this directory by specifying
the path in the ``$basePath`` property. This should not include the **migrations** or **seeds** directories, but
the path to the single directory that holds both of those sub-directories.

Helper Methods
==============

The **CIDatabaseTestCase** class provides several helper methods to aid in testing your database.

**seed($name)**

Allows you to manually load a Seed into the database. The only parameter is the name of the seed to run. The seed
must be present within the path specified in ``$basePath``.

**dontSeeInDatabase($table, $criteria)**

Asserts that a row with criteria matching the key/value pairs in ``$criteria`` DOES NOT exist in the database.
::

    $criteria = [
        'email'  => 'joe@example.com',
        'active' => 1
    ];
    $this->dontSeeInDatabase('users', $criteria);

**seeInDatabase($table, $criteria)**

Asserts that a row with criteria matching the key/value pairs in ``$criteria`` DOES exist in the database.
::

    $criteria = [
        'email'  => 'joe@example.com',
        'active' => 1
    ];
    $this->seeInDatabase('users', $criteria);

**grabFromDatabase($table, $column, $criteria)**

Returns the value of ``$column`` from the specified table where the row matches ``$criteria``. If more than one
row is found, it will only test against the first one.
::

    $username = $this->grabFromDatabase('users', 'username', ['email' => 'joe@example.com']);

**hasInDatabase($table, $data)**

Inserts a new row into the database. This row is removed after the current test runs. ``$data`` is an associative
array with the data to insert into the table.
::

    $data = [
        'email' => 'joe@example.com',
        'name'  => 'Joe Cool'
    ];
    $this->hasInDatabase('users', $data);

**seeNumRecords($expected, $table, $criteria)**

Asserts that a number of matching rows are found in the database that match ``$criteria``.
::

    $criteria = [
        'deleted' => 1
    ];
    $this->seeNumRecords(2, 'users', $criteria);

