Moodle Unit Testing with PHP Unit

I need to do some unit testing on Moodle 2.8 with a version that was pretty difficult to work on because of all the hacking of the core that had been done.

cd /mymoodle_install/public_html

#Run composer and install packages into /mymoodle_install/public_html/vendor
composer update

#Update the /mymoodle_install/public_html/config.php with test settings
$CFG->phpunit_prefix = ‘phpu_’;
$CFG->phpunit_dataroot = ‘/mymoodle_install/my_unit_tests’;

$CFG->phpunit_dbtype = ‘mysqli’; // ‘pgsql’, ‘mariadb’, ‘mysqli’, ‘mssql’, ‘sqlsrv’ or ‘oci’
$CFG->phpunit_dblibrary = ‘native’; // ‘native’ only at the moment
$CFG->phpunit_dbhost = ‘127.0.0.1’; // eg ‘localhost’ or ‘db.isp.com’ or IP
$CFG->phpunit_dbname = ‘test_db_name’; // database name, eg moodle
$CFG->phpunit_dbuser = ‘test’; // your database username
$CFG->phpunit_dbpass = ‘testpass’; // your database password

# Initialise the database
php admin/tool/phpunit/cli/init.php

#This failed as I was running MySQL 5.7.10 so I needed to change lib/dml/mysqli_native_moodle_database.php line 184
$sql = “SELECT @@storage_engine”;
#To
$sql = “SELECT @@default_storage_engine”;
# And change
$engine = $rec[‘@@storage_engine’];
# To
$engine = $rec[‘@@default_storage_engine’];

#Go to PHPStorm, bring up preferences (Mac), settings (Win)
Languages & Frameworks > PHP > PHPUnit

Use custom autoloader
/mymoodle_install/public_html/vendor/autoload.php

Test Runner
Tick ‘Default configuration file’
/mymoodle_install/public_html/phpunit.xml

Everything worked fine although the installation of Moodle that I was using was pretty broken and needed multiple updates to get the test database installing without errors.

One of the errors I needed to fix was code like this

SELECT c.id, c.fullname, c.summary from mdl_course c
                        INNER JOIN
                        mdl_block_course_bookmarks b ON c.id = b.seid

This caused a problem because using the full name of the table in SQL meant the unit test database was not able to find the right table (which would have been something like phpu_course). The correct way to do this was

SELECT c.id, c.fullname, c.summary from {course} c
                        INNER JOIN
                        {block_course_bookmarks} b ON c.id = b.seid

Moodle block ordering

Moodle has many ways to set the order of blocks on the page.

The easiest way is through the default weight variable

 

 

There are other ways to set this, one is through the

$CFG->defaultblocks_site

variable which appears in the config.php

Recently I had to change the order of a block and found that changing the display order through the block admin tool just didn’t work at all. After some searching, I found that it had been set in the theme in theme/<mytheme>/renderers/core_renderer.php

/** * Gets the blocks for the side region * @param $region * @return string * @throws coding_exception */public function blocks_for_side_pre($region) { $blockcontents = $this->page->blocks->get_content_for_region($region, $this); //Sort the blocks in defined order by closure variable $titles usort($blockcontents, function ($a_block, $b_block) { $titles = array('Block 1', 'Block 2', 'Block 3', 'Administration','Another block'); $a = array_search($a_block->title, $titles); $b = array_search($b_block->title, $titles); if(is_numeric($a) && is_numeric($b)){ if($a > $b) { return 1; } else { return -1; 嘉盛 } } return 0; }); $blocks = $this->page->blocks->get_blocks_for_region($region); $lastblock = null; $zones = array(); foreach ($blocks as $block) { $zones[] = $block->title; } $output = ''; foreach ($blockcontents as $bc) { if ($bc instanceof block_contents) { $output .= $this->block($bc, $region); $lastblock = $bc->title; } else if ($bc instanceof block_move_target) { $output .= $this->block_move_target($bc, $zones, $lastblock, $region); } else { throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.'); } } return $output;}

Moodle unit testing through simple test

I’ve used the simpletest PHP unit test framework and as Moodle uses the simpletest, I thought that setting up unit tests and running them would be straightforward. As with a lot of things Moodle, there are various gotchas which are not documented and it was only by searching through the source code that I was able to get them running.

I set up a unit test class and ran it through Site Administration -> Development -> Unit Tests. The first thing that went wrong was it complained that unittestprefix had not been set. A search through the source revealed that this variable refers the prefix you need to use on the test tables in the Moodle database. In my config.php, the setting was

//Unit testing. 
//This refers to the prefix for the test tables in the database
$CFG->unittestprefix = 'testing_';

The next problem was that Moodle couldn’t find my unit tests. It took a bit of digging but I found that I had prefix the name the php file with the unit test with ‘test_’. After that, my tests worked OK.

if (!defined('MOODLE_INTERNAL')) {
   ///  It must be included from a Moodle page
    die('Direct access to this script is forbidden.');    
}

global $DB, $CFG;

class test_block_rollover_clone_test extends UnitTestCase
{
    public function testInitialTesting()
    {
        $this->assertFalse(true);
    }

}

This resulted in the following:

Moodle form validation tips

There are a number of gotchas with Moodle Quick Form and it’s only through trial and error that I’ve discovered how they work.

The first is that when you implement any sort of validation, the form will only return data when the validation passes.

The second is that you don’t call any custom validation directly, you need to check that is_validated() is returning true.

class block_rollover_form extends moodleform
{

    function definition()
    {
        $form = $this->_form;
        //add form definitions here
        $form->addElement('date_selector', 'livedate', get_string('livedate', 'block_rollover'), array(
            'startyear' => 2012,
            'stopyear' => 2020
        ));

        $form->addElement('date_selector', 'archivedate', get_string('archivedate', 'block_rollover'), array(
            'startyear' => 2012,
            'stopyear' => 2020
        ));
        
    }

    /**
     * Custom validation to check that the start date and archive date are in future
     *
     * @param array $data
     * @param array $files
     * @return array
     */
    function validation($data, $files)
    {
        $errors = parent::validation($data, $files);
        $now = time();
        $live_date = $data['livedate'];
        $archive_date = $data['archivedate'];
        if ($live_date <= $now) {
            $errors['livedate'] = 'Please ensure that the start date for the new course is after today';
        }

        if ($archive_date <= $now) {
            $errors['archivedate'] = 'Please ensure that the archive date for the old course is after today';
        }
        return $errors;
    }


    /**
     * This function handles the form submission
     */
    public function handle()
    {
        //Do nothing if submitted or cancelled.
        if (!$this->is_submitted() || $this->is_cancelled()) {
            return;
        }

        //If the validation fails, it will print out the error messages on the form
        //note that $this->get_data() will return empty until the validation passes.
        if (!$this->is_validated()) {
            return;
        }

        //Form has been submitted and it validates, now we can get the data in the form
        $data = $this->get_data();

    }
}