Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Tests for class core_course_category methods invoking hooks.
  19   *
  20   * @package    core_course
  21   * @category   test
  22   * @copyright  2020 Ruslan Kabalin
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace tests\core_course;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  global $CFG;
  31  require_once($CFG->dirroot . '/course/tests/fixtures/mock_hooks.php');
  32  
  33  use PHPUnit\Framework\MockObject\MockObject;
  34  
  35  /**
  36   * Functional test for class core_course_category methods invoking hooks.
  37   */
  38  class core_course_category_hooks_testcase extends \advanced_testcase {
  39  
  40      protected function setUp(): void {
  41          $this->resetAfterTest();
  42          $this->setAdminUser();
  43      }
  44  
  45      /**
  46       * Provides mocked category configured for named callback function.
  47       *
  48       * get_plugins_callback_function will return callable prefixed with `tool_unittest_`,
  49       * the actual callbacks are defined in mock_hooks.php fixture file.
  50       *
  51       * @param core_course_category $category Category to mock
  52       * @param string $callback Callback function used in method we test.
  53       * @return MockObject
  54       */
  55      public function get_mock_category(\core_course_category $category, string $callback = '') : MockObject {
  56          // Setup mock object for \core_course_category.
  57          // Disable original constructor, since we can't use it directly since it is private.
  58          $mockcategory = $this->getMockBuilder(\core_course_category::class)
  59              ->setMethods(['get_plugins_callback_function'])
  60              ->disableOriginalConstructor()
  61              ->getMock();
  62  
  63          // Define get_plugins_callback_function use and return value.
  64          if (!empty($callback)) {
  65              $mockcategory->method('get_plugins_callback_function')
  66                  ->with($this->equalTo($callback))
  67                  ->willReturn(['tool_unittest_' . $callback]);
  68          }
  69  
  70          // Modify constructor visibility and invoke mock object with real object.
  71          // This is used to overcome private constructor.
  72          $reflected = new \ReflectionClass(\core_course_category::class);
  73          $constructor = $reflected->getConstructor();
  74          $constructor->setAccessible(true);
  75          $constructor->invoke($mockcategory, $category->get_db_record());
  76  
  77          return $mockcategory;
  78      }
  79  
  80      public function test_can_course_category_delete_hook() {
  81          $category1 = \core_course_category::create(array('name' => 'Cat1'));
  82          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
  83          $category3 = \core_course_category::create(array('name' => 'Cat3'));
  84  
  85          $mockcategory2 = $this->get_mock_category($category2, 'can_course_category_delete');
  86  
  87          // Add course to mocked clone of category2.
  88          $course1 = $this->getDataGenerator()->create_course(array('category' => $mockcategory2->id));
  89  
  90          // Now configure fixture to return false for the callback.
  91          mock_hooks::set_can_course_category_delete_return(false);
  92          $this->assertFalse($mockcategory2->can_delete_full($category3->id));
  93  
  94          // Now configure fixture to return true for the callback.
  95          mock_hooks::set_can_course_category_delete_return(true);
  96          $this->assertTrue($mockcategory2->can_delete_full($category3->id));
  97  
  98          // Verify passed arguments.
  99          $arguments = mock_hooks::get_calling_arguments();
 100          $this->assertCount(1, $arguments);
 101  
 102          // Argument 1 is the same core_course_category instance.
 103          $argument = array_shift($arguments);
 104          $this->assertSame($mockcategory2, $argument);
 105      }
 106  
 107      public function test_can_course_category_delete_move_hook() {
 108          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 109          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 110          $category3 = \core_course_category::create(array('name' => 'Cat3'));
 111  
 112          $mockcategory2 = $this->get_mock_category($category2, 'can_course_category_delete_move');
 113  
 114          // Add course to mocked clone of category2.
 115          $course1 = $this->getDataGenerator()->create_course(array('category' => $mockcategory2->id));
 116  
 117          // Now configure fixture to return false for the callback.
 118          mock_hooks::set_can_course_category_delete_move_return(false);
 119          $this->assertFalse($mockcategory2->can_move_content_to($category3->id));
 120  
 121          // Now configure fixture to return true for the callback.
 122          mock_hooks::set_can_course_category_delete_move_return(true);
 123          $this->assertTrue($mockcategory2->can_move_content_to($category3->id));
 124  
 125          // Verify passed arguments.
 126          $arguments = mock_hooks::get_calling_arguments();
 127          $this->assertCount(2, $arguments);
 128  
 129          // Argument 1 is the same core_course_category instance.
 130          $argument = array_shift($arguments);
 131          $this->assertSame($mockcategory2, $argument);
 132  
 133          // Argument 2 is referring to category 3.
 134          $argument = array_shift($arguments);
 135          $this->assertInstanceOf(\core_course_category::class, $argument);
 136          $this->assertEquals($category3->id, $argument->id);
 137      }
 138  
 139      public function test_pre_course_category_delete_hook() {
 140          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 141          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 142  
 143          $mockcategory2 = $this->get_mock_category($category2, 'pre_course_category_delete');
 144          $mockcategory2->delete_full();
 145  
 146          // Verify passed arguments.
 147          $arguments = mock_hooks::get_calling_arguments();
 148          $this->assertCount(1, $arguments);
 149  
 150          // Argument 1 is the category object.
 151          $argument = array_shift($arguments);
 152          $this->assertEquals($mockcategory2->get_db_record(), $argument);
 153      }
 154  
 155      public function test_pre_course_category_delete_move_hook() {
 156          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 157          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 158          $category3 = \core_course_category::create(array('name' => 'Cat3'));
 159  
 160          $mockcategory2 = $this->get_mock_category($category2, 'pre_course_category_delete_move');
 161  
 162          // Add course to mocked clone of category2.
 163          $course1 = $this->getDataGenerator()->create_course(array('category' => $mockcategory2->id));
 164  
 165          $mockcategory2->delete_move($category3->id);
 166  
 167          // Verify passed arguments.
 168          $arguments = mock_hooks::get_calling_arguments();
 169          $this->assertCount(2, $arguments);
 170  
 171          // Argument 1 is the same core_course_category instance.
 172          $argument = array_shift($arguments);
 173          $this->assertSame($mockcategory2, $argument);
 174  
 175          // Argument 2 is referring to category 3.
 176          $argument = array_shift($arguments);
 177          $this->assertInstanceOf(\core_course_category::class, $argument);
 178          $this->assertEquals($category3->id, $argument->id);
 179      }
 180  
 181      public function test_get_course_category_contents_hook() {
 182          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 183          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 184  
 185          $mockcategory2 = $this->get_mock_category($category2);
 186  
 187          // Define get_plugins_callback_function use in the mock, it is called twice for different callback in the form.
 188          $mockcategory2->expects($this->exactly(2))
 189              ->method('get_plugins_callback_function')
 190              ->withConsecutive(
 191                  [$this->equalTo('can_course_category_delete')],
 192                  [$this->equalTo('get_course_category_contents')]
 193              )
 194              ->willReturn(
 195                  ['tool_unittest_can_course_category_delete'],
 196                  ['tool_unittest_get_course_category_contents']
 197              );
 198  
 199          // Now configure fixture to return string for the callback.
 200          $content = 'Bunch of test artefacts';
 201          mock_hooks::set_get_course_category_contents_return($content);
 202  
 203          $mform = new \core_course_deletecategory_form(null, $mockcategory2);
 204          $this->expectOutputRegex("/<li>$content<\/li>/");
 205          $mform->display();
 206  
 207          // Verify passed arguments.
 208          $arguments = mock_hooks::get_calling_arguments();
 209          $this->assertCount(1, $arguments);
 210  
 211          // Argument 1 is the same core_course_category instance.
 212          $argument = array_shift($arguments);
 213          $this->assertSame($mockcategory2, $argument);
 214      }
 215  }