2
\$\begingroup\$

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...

  1. instead of creating stubs at the top of the file, I should do it with native PHPUnit methods such as $this->createMock(), and

  2. 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;
}

// ...

PHPUnit output

enter image description here

\$\endgroup\$
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.