<commit_before># CI PHPUnit Test for CodeIgniter 3.0

version: **master** | 
[v0.5.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.5.0/docs/HowToWriteTests.md) | 
[v0.4.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.4.0/docs/HowToWriteTests.md) | 
[v0.3.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.3.0/docs/HowToWriteTests.md) | 
[v0.2.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.2.0/docs/HowToWriteTests.md)

## How to Write Tests

- [Introduction](#introduction)
- [Testing Environment](#testing-environment)
- [Can and Can't](#can-and-cant)
	- [MY_Loader](#my_loader)
	- [`exit()`](#exit)
	- [Reset CodeIgniter object](#reset-codeigniter-object)
	- [Hooks](#hooks)
- [Models](#models)
	- [Using Database](#using-database)
	- [Database Seeding](#database-seeding)
	- [Using PHPUnit Mock Objects](#using-phpunit-mock-objects)
- [Controllers](#controllers)
	- [Request to Controller](#request-to-controller)
	- [Request to URI string](#request-to-uri-string)
	- [Request and Use Mocks](#request-and-use-mocks)
	- [Ajax Request](#ajax-request)
	- [Examine DOM in Controller Output](#examine-dom-in-controller-output)
	- [Controller with Authentication](#controller-with-authentication)
	- [`redirect()`](#redirect)
	- [`show_error()` and `show_404()`](#show_error-and-show_404)
	- [Controller with Hooks](#controller-with-hooks)
	- [Controller with Name Collision](#controller-with-name-collision)
- [Mock Libraries](#mock-libraries)
- [Monkey Patching](#monkey-patching)
	- [Converting `exit()` to Exception](#converting-exit-to-exception)
	- [Patching Functions](#patching-functions)
	- [Patching Methods in User-defined Classes](#patching-methods-in-user-defined-classes)
- [More Samples](#more-samples)

### Introduction

Here is my advice:

* You don't have to write your business logic in your controllers. Write them in your models.
* You should test models first, and test them well.

And PHPUnit has great documentation. You should read [Writing Tests for PHPUnit](https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html).

### Testing Environment

Tests always run on `testing` environment.

If you don't know well about config files and environments, see [CodeIgniter User Guide](http://www.codeigniter.com/user_guide/libraries/config.html#environments).

### Can and Can't

*CI PHPUnit Test* does not want to modify CodeIgniter files. The more you modify them, the more you get difficulities when you update CodeIgniter.

In fact, it uses a modified class and a few functions. But I try to modify as little as possible.

The core functions and a class which are modified:

* function `load_class()`
* function `is_loaded()`
* function `is_cli()`
* function `show_error()`
* function `show_404()`
* function `set_status_header()`
* class `CI_Loader`

and a helper which is modified:

* function `redirect()` in URL helper

All of them are in `tests/_ci_phpunit_test/replacing` folder.

And *CI PHPUnit Test* adds a property dynamically:

* property `CI_Output::_status`

#### MY_Loader

*CI PHPUnit Test* replaces `CI_Loader` and modifies below methods:

* `CI_Loader::model()`
* `CI_Loader::_ci_load_library()`
* `CI_Loader::_ci_load_stock_library()`

But if you place MY_Loader, your MY_Loader extends the loader of *CI PHPUnit Test*.

If your MY_Loader overrides the above methods, probably *CI PHPUnit Test* does not work correctly.

#### `exit()`

When a test exercises code that contains `exit()` or `die()` statement, the execution of the whole test suite is aborted.

For example, if you write `exit()` in your controller code, your testing ends with it.

I recommend you not to use `exit()` or `die()` in your code.

**Monkey Patching on `exit()`**

*CI PHPUnit Test* has functionality that makes all `exit()` and `die()` in your code throw `CIPHPUnitTestExitException`.

See [Monkey Patching](#monkey-patching) for details.

**`show_error()` and `show_404()`**

And *CI PHPUnit Test* has special [show_error() and show_404()](#show_error-and-show_404).

**`redirect()`**

*CI PHPUnit Test* replaces `redirect()` function in URL helper. Using it, you can easily test controllers that contain `redirect()`. See [`redirect()`](#redirect) for details.

#### Reset CodeIgniter object

CodeIgniter has a function `get_instance()` to get the CodeIgniter object (CodeIgniter instance or CodeIgniter super object).

*CI PHPUnit Test* has a new function `reset_instance()` which reset the current CodeIgniter object. After resetting, you can (and must) create a new your Controller instance with new state.

#### Hooks

If you enable CodeIgniter's hooks, hook `pre_system` is called once in PHPUnit bootstrap.

If you use `$this->request->enableHooks()` and `$this->request()`, hook `pre_controller`, `post_controller_constructor` and `post_controller` are called on every `$this->request()` to a controller.

See [Controller with Hooks](#controller-with-hooks) for details.

### Models

#### Using Database

*tests/models/Inventory_model_test.php*
~~~php
<?php

class Inventory_model_test extends TestCase
{
	public function setUp()
	{
		$this->CI =& get_instance();
		$this->CI->load->model('shop/Inventory_model');
		$this->obj = $this->CI->Inventory_model;
	}

	public function test_get_category_list()
	{
		$expected = [
			1 => 'Book',
			2 => 'CD',
			3 => 'DVD',
		];
		$list = $this->obj->get_category_list();
		foreach ($list as $category) {
			$this->assertEquals($expected[$category->id], $category->name);
		}
	}

	public function test_get_category_name()
	{
		$actual = $this->obj->get_category_name(1);
		$expected = 'Book';
		$this->assertEquals($expected, $actual);
	}
}
~~~

Test case class extends [TestCase](FunctionAndClassReference.md#class-testcase) class.

Don't forget to write `parent::setUpBeforeClass();` if you override `setUpBeforeClass()` method.

See [working sample](https://github.com/kenjis/ci-app-for-ci-phpunit-test/blob/master/application/tests/models/Category_model_test.php).

#### Database Seeding

I put [Seeder Library](../application/libraries/Seeder.php) and a sample [Seeder File](../application/database/seeds/CategorySeeder.php).

They are not installed, so if you want to use, copy them manually.

You can use them like below:

~~~php
	public static function setUpBeforeClass()
	{
		parent::setUpBeforeClass();

		$CI =& get_instance();<commit_msg>Update docs<commit_after># CI PHPUnit Test for CodeIgniter 3.0

version: **master** | 
[v0.5.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.5.0/docs/HowToWriteTests.md) | 
[v0.4.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.4.0/docs/HowToWriteTests.md) | 
[v0.3.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.3.0/docs/HowToWriteTests.md) | 
[v0.2.0](https://github.com/kenjis/ci-phpunit-test/blob/v0.2.0/docs/HowToWriteTests.md)

## How to Write Tests

- [Introduction](#introduction)
- [Testing Environment](#testing-environment)
- [Can and Can't](#can-and-cant)
	- [MY_Loader](#my_loader)
	- [`exit()`](#exit)
	- [Reset CodeIgniter object](#reset-codeigniter-object)
	- [Hooks](#hooks)
- [Models](#models)
	- [Using Database](#using-database)
	- [Database Seeding](#database-seeding)
	- [Using PHPUnit Mock Objects](#using-phpunit-mock-objects)
- [Controllers](#controllers)
	- [Request to Controller](#request-to-controller)
	- [Request to URI string](#request-to-uri-string)
	- [Request and Use Mocks](#request-and-use-mocks)
	- [Ajax Request](#ajax-request)
	- [Examine DOM in Controller Output](#examine-dom-in-controller-output)
	- [Controller with Authentication](#controller-with-authentication)
	- [`redirect()`](#redirect)
	- [`show_error()` and `show_404()`](#show_error-and-show_404)
	- [Controller with Hooks](#controller-with-hooks)
	- [Controller with Name Collision](#controller-with-name-collision)
- [Mock Libraries](#mock-libraries)
- [Monkey Patching](#monkey-patching)
	- [Converting `exit()` to Exception](#converting-exit-to-exception)
	- [Patching Functions](#patching-functions)
	- [Patching Methods in User-defined Classes](#patching-methods-in-user-defined-classes)
- [More Samples](#more-samples)

### Introduction

Here is my advice:

* You don't have to write your business logic in your controllers. Write them in your models.
* You should test models first, and test them well.

And PHPUnit has great documentation. You should read [Writing Tests for PHPUnit](https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html).

### Testing Environment

Tests always run on `testing` environment.

If you don't know well about config files and environments, see [CodeIgniter User Guide](http://www.codeigniter.com/user_guide/libraries/config.html#environments).

### Can and Can't

*CI PHPUnit Test* does not want to modify CodeIgniter files. The more you modify them, the more you get difficulities when you update CodeIgniter.

In fact, it uses a modified class and a few functions. But I try to modify as little as possible.

The core functions and a class which are modified:

* function `load_class()`
* function `is_loaded()`
* function `is_cli()`
* function `show_error()`
* function `show_404()`
* function `set_status_header()`
* class `CI_Loader`

and a helper which is modified:

* function `redirect()` in URL helper

All of them are in `tests/_ci_phpunit_test/replacing` folder.

And *CI PHPUnit Test* adds a property dynamically:

* property `CI_Output::_status`

#### MY_Loader

*CI PHPUnit Test* replaces `CI_Loader` and modifies below methods:

* `CI_Loader::model()`
* `CI_Loader::_ci_load_library()`
* `CI_Loader::_ci_load_stock_library()`

But if you place MY_Loader, your MY_Loader extends the loader of *CI PHPUnit Test*.

If your MY_Loader overrides the above methods, probably *CI PHPUnit Test* does not work correctly.

#### `exit()`

When a test exercises code that contains `exit()` or `die()` statement, the execution of the whole test suite is aborted.

For example, if you write `exit()` in your controller code, your testing ends with it.

I recommend you not to use `exit()` or `die()` in your code.

**Monkey Patching on `exit()`**

*CI PHPUnit Test* has functionality that makes all `exit()` and `die()` in your code throw `CIPHPUnitTestExitException`.

See [Monkey Patching](#monkey-patching) for details.

**`show_error()` and `show_404()`**

And *CI PHPUnit Test* has special [show_error() and show_404()](#show_error-and-show_404).

**`redirect()`**

*CI PHPUnit Test* replaces `redirect()` function in URL helper. Using it, you can easily test controllers that contain `redirect()`. See [`redirect()`](#redirect) for details.

#### Reset CodeIgniter object

CodeIgniter has a function `get_instance()` to get the CodeIgniter object (CodeIgniter instance or CodeIgniter super object).

*CI PHPUnit Test* has a new function `reset_instance()` which reset the current CodeIgniter object. After resetting, you can (and must) create a new your Controller instance with new state.

#### Hooks

If you enable CodeIgniter's hooks, hook `pre_system` is called once in PHPUnit bootstrap.

If you use `$this->request->enableHooks()` and `$this->request()`, hook `pre_controller`, `post_controller_constructor` and `post_controller` are called on every `$this->request()` to a controller.

See [Controller with Hooks](#controller-with-hooks) for details.

### Models

#### Using Database

*tests/models/Inventory_model_test.php*
~~~php
<?php

class Inventory_model_test extends TestCase
{
	public function setUp()
	{
		$this->CI =& get_instance();
		$this->CI->load->model('shop/Inventory_model');
		$this->obj = $this->CI->Inventory_model;
	}

	public function test_get_category_list()
	{
		$expected = [
			1 => 'Book',
			2 => 'CD',
			3 => 'DVD',
		];
		$list = $this->obj->get_category_list();
		foreach ($list as $category) {
			$this->assertEquals($expected[$category->id], $category->name);
		}
	}

	public function test_get_category_name()
	{
		$actual = $this->obj->get_category_name(1);
		$expected = 'Book';
		$this->assertEquals($expected, $actual);
	}
}
~~~

Test case class extends [TestCase](FunctionAndClassReference.md#class-testcase) class.

Don't forget to write `parent::setUpBeforeClass();` if you override `setUpBeforeClass()` method.

Don't forget to write `parent::tearDown();` if you override `tearDown()` method.

See [working sample](https://github.com/kenjis/ci-app-for-ci-phpunit-test/blob/master/application/tests/models/Category_model_test.php).

#### Database Seeding

I put [Seeder Library](../application/libraries/Seeder.php) and a sample [Seeder File](../application/database/seeds/CategorySeeder.php).

They are not installed, so if you want to use, copy them manually.

You can use them like below:

~~~php
	public static function setUpBeforeClass()
	{
		parent::setUpBeforeClass();

		$CI =& get_instance();
{'fraction_non_alphanumeric': 0.1436286787726988, 'fraction_numerical': 0.007514088916718848, 'mean_word_length': 5.021206409048068, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 47, 'https://': 12, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7521766', 'n_tokens_mistral': 4278, 'n_tokens_neox': 4008, 'n_words': 1190}