diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index b403b7b64cff..d90b2e08327e 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -40,6 +40,10 @@ namespace CodeIgniter\Test; use CodeIgniter\Events\Events; +use CodeIgniter\Session\Handlers\ArrayHandler; +use CodeIgniter\Test\Mock\MockEmail; +use CodeIgniter\Test\Mock\MockSession; +use Config\Services; use PHPUnit\Framework\TestCase; /** @@ -55,6 +59,37 @@ class CIUnitTestCase extends TestCase */ protected $app; + /** + * Methods to run during setUp. + * + * @var array of methods + */ + protected $setUpMethods = [ + 'mockEmail', + 'mockSession', + ]; + + /** + * Methods to run during tearDown. + * + * @var array of methods + */ + protected $tearDownMethods = []; + + //-------------------------------------------------------------------- + // Staging + //-------------------------------------------------------------------- + + /** + * Load the helpers. + */ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + helper(['url', 'test']); + } + protected function setUp(): void { parent::setUp(); @@ -64,9 +99,51 @@ protected function setUp(): void $this->app = $this->createApplication(); } - helper('url'); + foreach ($this->setUpMethods as $method) + { + $this->$method(); + } + } + + protected function tearDown(): void + { + parent::tearDown(); + + foreach ($this->tearDownMethods as $method) + { + $this->$method(); + } } + //-------------------------------------------------------------------- + // Mocking + //-------------------------------------------------------------------- + + /** + * Injects the mock session driver into Services + */ + protected function mockSession() + { + $_SESSION = []; + + $config = config('App'); + $session = new MockSession(new ArrayHandler($config, '0.0.0.0'), $config); + + Services::injectMock('session', $session); + } + + /** + * Injects the mock email driver so no emails really send + */ + protected function mockEmail() + { + Services::injectMock('email', new MockEmail(config('Email'))); + } + + //-------------------------------------------------------------------- + // Assertions + //-------------------------------------------------------------------- + /** * Custom function to hook into CodeIgniter's Logging mechanism * to check if certain messages were logged during code execution. @@ -236,6 +313,10 @@ public function assertCloseEnoughString($expected, $actual, string $message = '' } } + //-------------------------------------------------------------------- + // Utility + //-------------------------------------------------------------------- + /** * Loads up an instance of CodeIgniter * and gets the environment setup. @@ -247,16 +328,14 @@ protected function createApplication() return require realpath(__DIR__ . '/../') . '/bootstrap.php'; } - //-------------------------------------------------------------------- /** * Return first matching emitted header. * - * @param string $header Identifier of the header of interest - * @param bool $ignoreCase + * @param string $header Identifier of the header of interest + * @param boolean $ignoreCase * * @return string|null The value of the header found, null if not found */ - // protected function getHeaderEmitted(string $header, bool $ignoreCase = false): ?string { $found = false; @@ -279,5 +358,4 @@ protected function getHeaderEmitted(string $header, bool $ignoreCase = false): ? return null; } - } diff --git a/user_guide_src/source/testing/overview.rst b/user_guide_src/source/testing/overview.rst index 96646d62e194..d7b1ba0767ba 100644 --- a/user_guide_src/source/testing/overview.rst +++ b/user_guide_src/source/testing/overview.rst @@ -101,6 +101,51 @@ have the correct namespace relative to ``App``. When testing database results, you must use the `CIDatabaseTestClass `_ class. +Staging +------- + +Most tests require some preparation in order to run correctly. PHPUnit's ``TestCase`` provides four methods +to help with staging and clean up:: + + public static function setUpBeforeClass(): void + public static function tearDownAfterClass(): void + public function setUp(): void + public function tearDown(): void + +The static methods run before and after the entire test case, whereas the local methods run +between each test. If you implement any of these special functions make sure you run their +parent as well so extended test cases do not interfere with staging:: + + public function setUp(): void + { + parent::setUp(); + helper('text'); + } + +In addition to these methods, ``CIUnitTestCase`` also comes with a convenience property for +parameter-free methods you want run during set up and tear down:: + + protected $setUpMethods = [ + 'mockEmail', + 'mockSession', + ]; + + protected $tearDownMethods = []; + +You can see by default these handle the mocking of intrusive services, but your class may override +that or provide their own:: + + class OneOfMyModelsTest extends CIUnitTestCase + { + protected $tearDownMethods = [ + 'purgeRows', + ]; + + protected function purgeRows() + { + $this->model->purgeDeleted() + } + Additional Assertions --------------------- @@ -262,7 +307,7 @@ class exactly. The second parameter is the instance to replace it with. Removes all mocked classes from the Services class, bringing it back to its original state. - +.. note:: The ``Email`` and ``Session`` services are mocked by default to prevent intrusive testing behavior. To prevent these from mocking remove their method callback from the class property: ``$setUpMethods = ['mockEmail', 'mockSession'];`` Stream Filters ==============