I want to unit test my PHP website, which uses the outdated framework CodeIgniter 3. CodeIgniter 3 uses the MVC pattern, and each page on the website corresponds to a public method in a controller.
I wrote a simple unit test (to check and see if the page's title is set to "About Us") for one of the pages/methods as a proof of concept. It tests this line:
$this->data = set_page_title('About Us', $this->data);
... by making this assertion...
$this->assertEquals( $titleGlobal, 'About Us' );
But boy does it take a lot of setup code to test this simple assertion.
I got the test for this working, but it's ugly. I suspect code review is going to reveal...
instead of creating stubs at the top of the file, I should do it with native PHPUnit methods such as $this->createMock(), and
I need to replace all my functions with dependency injected classes and methods, for easier stubbing/mocking
Would like someone good at PHPUnit to confirm this and add some details. Thanks a lot :)
The name of the controller is General, and the name of the page/method is about_us.
Just looking for a code review of tests/controllers/GeneralTest.php. Some other code is included for background.
tests/controllers/GeneralTest.php
<?php
use PHPUnit\Framework\TestCase;
if ( ! defined('BASEPATH') ) {
define('BASEPATH', true);
}
class CI_Controller {
function __construct() {
$this->output = new Output();
}
}
class Output {
function enable_profiler() {
// intentionally blank since this is a test double
}
}
function set_auth_variables( $data ) {
// intentionally blank since this is a test double
}
function load_page_with_great_races_sidebar( $uri, $data ) {
// intentionally blank since this is a test double
}
$titleGlobal = '';
function set_page_title( $title, $data ) {
global $titleGlobal;
$titleGlobal = $title;
}
final class GeneralTest extends TestCase {
protected function setUp(): void {
define( 'APPPATH', 'application/' );
define( 'ENVIRONMENT', 'development' );
require_once( 'application/config/constants.php' );
require_once( 'application/controllers/General.php' );
}
public function test__about_us__has_correct_title() {
global $titleGlobal;
$controller = new General();
$controller->about_us();
$this->assertEquals( $titleGlobal, 'About Us' );
}
}
application/controllers/General.php
<?php
require_once APPPATH . 'core/MV_Base_Class.php';
class General extends MV_Base_Class {
public function __construct() {
parent::__construct();
}
public function about_us() {
$this->data = set_page_title('About Us', $this->data);
load_page_with_great_races_sidebar('general/about_us', $this->data);
}
// ...
}
application/core/MV_Base_Class.php
<?php
class MV_Base_Class extends CI_Controller {
protected $data = null;
public function __construct() {
// Load system/core/Controller, which loads system/core/Loader. This creates the database connection and loads helper files, among other things.
// If there is an SQL connection problem, the line below is where the code will die.
parent::__construct();
$this->data = set_auth_variables($this->data);
// If ?r= is used in the link, record this in a session variable. ("r" stands for referrer and is a marketing tracking variable.) Later, if the person creates a company or signs up to volunteer, the "r" variable will be associated with their signup, which helps populate /managers/signup_sources and similar reports.
if ( isset($_GET['r']) ) {
$_SESSION['http_referrer'] = $_GET['r'];
}
if ( ENVIRONMENT == 'development' ) {
$this->output->enable_profiler(true);
}
// Adds 0.04s to every page load
if ( ENVIRONMENT == 'development' ) {
require_once('../ci_dev/vendor/autoload.php');
} else {
require_once('../ci_main/vendor/autoload.php');
}
}
}
application/helpers/mv_login_helper.php
<?php
if ( ! defined('BASEPATH')) {
exit('No direct script access allowed');
}
function set_auth_variables($data) {
$obj = &get_instance();
$manager_id = $obj->session->userdata('manager_id');
// Set auth variables even if user isn't logged in. That way you don't get "variable not set" errors, and we save ourselves from having to check for that.
$data['auth'] = null;
if ( $manager_id ) {
$data['auth']['manager'] = $obj->manager_model->get_manager_by_id($manager_id);
if ( $data['auth']['manager'] ) {
$company_id = $data['auth']['manager']['manager_company_id'];
$data['auth']['company'] = $obj->company_model->get_company_by_id($company_id);
} else {
$data['auth'] = null;
}
}
if ( isset($_SESSION['admin_sticky_company_id']) ) {
if ( is_admin($data['auth']) && $_SESSION['admin_sticky_company_id'] != $data['auth']['company']['company_id'] ) {
$data['auth']['company'] = $obj->company_model->get_company_by_id($_SESSION['admin_sticky_company_id']);
}
}
// If company has been deleted, unset admin_sticky_company_id to avoid Control Panel redirect loop.
if ( ! isset($data['auth']['company']) && isset($_SESSION['admin_sticky_company_id']) ) {
unset($_SESSION['admin_sticky_company_id']);
}
return $data;
}
// ...
application/helpers/mv_view_helper.php
<?php
if ( ! defined('BASEPATH')) {
exit('No direct script access allowed');
}
function load_page_with_great_races_sidebar($page_name, $data) {
// $data is required. If the user is logged in, we need the auth variables.
$data = html_escape($data);
$obj = &get_instance();
$data['great_races'] = $obj->race_model->get_upcoming_sidebar_races();
$obj->load->view('templates/header', $data);
$obj->load->view('templates/great_events_header', $data);
$obj->load->view($page_name, $data);
$obj->load->view('templates/great_events_footer', $data);
$obj->load->view('templates/footer', $data);
}
// ...
application/helpers/mv_misc_helper.php
<?php
if ( ! defined('BASEPATH')) {
exit('No direct script access allowed');
}
function set_page_title($page_title, $data) {
$data['page_title'] = $page_title . ' - ' . MV_BROWSER_TITLE;
return $data;
}
// ...