From 8dcad876310d7e2ccfe2430823cbe4f0f4e9f40d Mon Sep 17 00:00:00 2001
From: Andrew Nicols <andrew@nicols.co.uk>
Date: Tue, 15 Oct 2024 22:50:40 +0800
Subject: [PATCH] MDL-81308 core: Update tests to not use withConsecutive

---
 .upgradenotes/MDL-81308-2024101712591343.yml  |  19 +++
 .../tests/expired_contexts_test.php           |  72 ++++++++---
 admin/tool/langimport/tests/locale_test.php   |  20 ++-
 admin/tool/usertours/tests/tour_test.php      |  55 +++++++--
 .../tests/activity_custom_completion_test.php |  13 +-
 course/tests/category_hooks_test.php          |  23 ++--
 files/tests/converter_test.php                |  45 ++++---
 lib/filestorage/tests/file_system_test.php    |   9 +-
 lib/tests/completionlib_test.php              |  97 +++++++++++----
 .../exportable_filearea_test.php              |  13 +-
 .../exportable_textarea_test.php              |  13 +-
 repository/dropbox/tests/api_test.php         | 116 +++++++++++-------
 .../tests/googledocs_search_content_test.php  |  48 ++++----
 .../browser/googledocs_drive_content_test.php |  49 ++++----
 14 files changed, 397 insertions(+), 195 deletions(-)
 create mode 100644 .upgradenotes/MDL-81308-2024101712591343.yml

diff --git a/.upgradenotes/MDL-81308-2024101712591343.yml b/.upgradenotes/MDL-81308-2024101712591343.yml
new file mode 100644
index 00000000000..749e120178a
--- /dev/null
+++ b/.upgradenotes/MDL-81308-2024101712591343.yml
@@ -0,0 +1,19 @@
+issueNumber: MDL-81308
+notes:
+  core:
+    - message: >
+        All uses of the following PHPUnit methods have been removed as these
+        methods are
+
+        deprecated upstream without direct replacement:
+
+
+        - `withConsecutive`
+
+        - `willReturnConsecutive`
+
+        - `onConsecutive`
+
+
+        Any plugin using these methods must update their uses.
+      type: changed
diff --git a/admin/tool/dataprivacy/tests/expired_contexts_test.php b/admin/tool/dataprivacy/tests/expired_contexts_test.php
index daa8cdf4750..ffd5d51a57a 100644
--- a/admin/tool/dataprivacy/tests/expired_contexts_test.php
+++ b/admin/tool/dataprivacy/tests/expired_contexts_test.php
@@ -1370,12 +1370,20 @@ class expired_contexts_test extends \advanced_testcase {
             ])
             ->getMock();
         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
-        $mockprivacymanager->expects($this->exactly(2))
+        $deleteinvocations = $this->exactly(2);
+        $mockprivacymanager->expects($deleteinvocations)
             ->method('delete_data_for_all_users_in_context')
-            ->withConsecutive(
-                [$blockcontext],
-                [$usercontext]
-            );
+            ->willReturnCallback(function ($context) use (
+                $deleteinvocations,
+                $blockcontext,
+                $usercontext,
+            ): void {
+                match ($deleteinvocations->getInvocationCount()) {
+                    1 => $this->assertEquals($blockcontext, $context),
+                    2 => $this->assertEquals($usercontext, $context),
+                    default => $this->fail('Unexpected invocation count'),
+                };
+            });
 
         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
             ->onlyMethods(['get_privacy_manager'])
@@ -1588,12 +1596,20 @@ class expired_contexts_test extends \advanced_testcase {
             ])
             ->getMock();
         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
-        $mockprivacymanager->expects($this->exactly(2))
+        $deleteinvocations = $this->exactly(2);
+        $mockprivacymanager->expects($deleteinvocations)
             ->method('delete_data_for_all_users_in_context')
-            ->withConsecutive(
-                [$blockcontext],
-                [$usercontext]
-            );
+            ->willReturnCallback(function ($context) use (
+                $deleteinvocations,
+                $blockcontext,
+                $usercontext,
+            ): void {
+                match ($deleteinvocations->getInvocationCount()) {
+                    1 => $this->assertEquals($blockcontext, $context),
+                    2 => $this->assertEquals($usercontext, $context),
+                    default => $this->fail('Unexpected invocation count'),
+                };
+            });
 
         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
             ->onlyMethods(['get_privacy_manager'])
@@ -1641,12 +1657,20 @@ class expired_contexts_test extends \advanced_testcase {
             ])
             ->getMock();
         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
-        $mockprivacymanager->expects($this->exactly(2))
+        $deleteinvocations = $this->exactly(2);
+        $mockprivacymanager->expects($deleteinvocations)
             ->method('delete_data_for_all_users_in_context')
-            ->withConsecutive(
-                [$blockcontext],
-                [$usercontext]
-            );
+            ->willReturnCallback(function ($context) use (
+                $deleteinvocations,
+                $blockcontext,
+                $usercontext,
+            ): void {
+                match ($deleteinvocations->getInvocationCount()) {
+                    1 => $this->assertEquals($blockcontext, $context),
+                    2 => $this->assertEquals($usercontext, $context),
+                    default => $this->fail('Unexpected invocation count'),
+                };
+            });
 
         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
             ->onlyMethods(['get_privacy_manager'])
@@ -1860,12 +1884,20 @@ class expired_contexts_test extends \advanced_testcase {
             ])
             ->getMock();
         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
-        $mockprivacymanager->expects($this->exactly(2))
+        $deleteinvocations = $this->exactly(2);
+        $mockprivacymanager->expects($deleteinvocations)
             ->method('delete_data_for_all_users_in_context')
-            ->withConsecutive(
-                [$forumcontext],
-                [$coursecontext]
-            );
+            ->willReturnCallback(function ($context) use (
+                $deleteinvocations,
+                $forumcontext,
+                $coursecontext,
+            ): void {
+                match ($deleteinvocations->getInvocationCount()) {
+                    1 => $this->assertEquals($forumcontext, $context),
+                    2 => $this->assertEquals($coursecontext, $context),
+                    default => $this->fail('Unexpected invocation count'),
+                };
+            });
 
         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
             ->onlyMethods(['get_privacy_manager'])
diff --git a/admin/tool/langimport/tests/locale_test.php b/admin/tool/langimport/tests/locale_test.php
index aab50f7edc3..ad0eed13d77 100644
--- a/admin/tool/langimport/tests/locale_test.php
+++ b/admin/tool/langimport/tests/locale_test.php
@@ -55,8 +55,14 @@ final class locale_test extends \advanced_testcase {
                 'set_locale',
             ])
             ->getMock();
-        $mock->method('get_locale')->will($this->onConsecutiveCalls('en'));
-        $mock->method('set_locale')->will($this->onConsecutiveCalls('es', 'en'));
+        $mock->method('get_locale')->will($this->returnValue('en'));
+        $setinvocations = $this->exactly(2);
+        $mock
+            ->expects($setinvocations)
+            ->method('set_locale')->willReturnCallback(fn () => match ($setinvocations->getInvocationCount()) {
+                1 => 'es',
+                2 => 'en',
+            });
 
         // Test what happen when locale is available on system.
         $result = $mock->check_locale_availability('en');
@@ -72,8 +78,14 @@ final class locale_test extends \advanced_testcase {
                 'set_locale',
             ])
             ->getMock();
-        $mock->method('get_locale')->will($this->onConsecutiveCalls('en'));
-        $mock->method('set_locale')->will($this->onConsecutiveCalls(false, 'en'));
+        $mock->expects($this->exactly(1))->method('get_locale')->will($this->returnValue('en'));
+        $setinvocations = $this->exactly(2);
+        $mock
+            ->expects($setinvocations)
+            ->method('set_locale')->willReturnCallback(fn () => match ($setinvocations->getInvocationCount()) {
+                1 => false,
+                2 => 'en',
+            });
 
         // Test what happen when locale is not available on system.
         $result = $mock->check_locale_availability('en');
diff --git a/admin/tool/usertours/tests/tour_test.php b/admin/tool/usertours/tests/tour_test.php
index 54d0165fa04..ebc27d30702 100644
--- a/admin/tool/usertours/tests/tour_test.php
+++ b/admin/tool/usertours/tests/tour_test.php
@@ -527,14 +527,30 @@ class tour_test extends \advanced_testcase {
         // Mock the database.
         $DB = $this->mock_database();
 
-        $DB->expects($this->exactly(3))
+        $deleteinvocations = $this->exactly(3);
+        $DB->expects($deleteinvocations)
             ->method('delete_records')
-            ->withConsecutive(
-                [$this->equalTo('tool_usertours_tours'), $this->equalTo(['id' => $id])],
-                [$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_LAST_COMPLETED_BY_USER . $id])],
-                [$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_REQUESTED_BY_USER . $id])]
-            )
-            ->willReturn(null);
+            ->willReturnCallback(function ($table, $conditions) use ($deleteinvocations, $id) {
+                switch ($deleteinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals('tool_usertours_tours', $table);
+                        $this->assertEquals(['id' => $id], $conditions);
+                        return null;
+                        break;
+                    case 2:
+                        $this->assertEquals('user_preferences', $table);
+                        $this->assertEquals(['name' => tour::TOUR_LAST_COMPLETED_BY_USER . $id], $conditions);
+                        return null;
+                        break;
+                    case 3:
+                        $this->assertEquals('user_preferences', $table);
+                        $this->assertEquals(['name' => tour::TOUR_REQUESTED_BY_USER . $id], $conditions);
+                        return null;
+                        break;
+                    default:
+                        $this->fail('Unexpected call to delete_records');
+                }
+            });
 
         $DB->expects($this->once())
             ->method('get_records')
@@ -554,7 +570,7 @@ class tour_test extends \advanced_testcase {
         for ($i = 4; $i >= 0; $i--) {
             $id = rand($i * 10, ($i * 10) + 9);
             $mockdata[] = (object) ['id' => $id];
-            $expectations[] = [$this->equalTo('tool_usertours_steps'), $this->equalTo('sortorder'), 4 - $i, ['id' => $id]];
+            $expectations[] = [4 - $i, ['id' => $id]];
         }
 
         // Mock the database.
@@ -563,9 +579,19 @@ class tour_test extends \advanced_testcase {
             ->method('get_records')
             ->willReturn($mockdata);
 
-        $setfield = $DB->expects($this->exactly(5))
-            ->method('set_field');
-        call_user_func_array([$setfield, 'withConsecutive'], $expectations);
+        $setfieldinvocations = $this->exactly(5);
+        $DB->expects($setfieldinvocations)
+            ->method('set_field')
+            ->willReturnCallback(function ($table, $field, $value, $conditions) use (
+                $setfieldinvocations,
+                $expectations,
+            ): void {
+                $expectation = $expectations[$setfieldinvocations->getInvocationCount() - 1];
+                $this->assertEquals('tool_usertours_steps', $table);
+                $this->assertEquals('sortorder', $field);
+                $this->assertEquals($expectation[0], $value);
+                $this->assertEquals($expectation[1], $conditions);
+            });
 
         $tour->reset_step_sortorder();
     }
@@ -764,9 +790,12 @@ class tour_test extends \advanced_testcase {
             ->getMock();
 
         if ($getconfig) {
-            $tour->expects($this->exactly(count($getconfig)))
+            $getinvocations = $this->exactly(count($getconfig));
+            $tour->expects($getinvocations)
                 ->method('get_config')
-                ->will(call_user_func_array([$this, 'onConsecutiveCalls'], $getconfig));
+                ->willReturnCallback(function () use ($getinvocations, $getconfig) {
+                    return $getconfig[$getinvocations->getInvocationCount() - 1];
+                });
         }
 
         if ($setconfig) {
diff --git a/completion/tests/activity_custom_completion_test.php b/completion/tests/activity_custom_completion_test.php
index 07ed75a3fe8..79fc49442ac 100644
--- a/completion/tests/activity_custom_completion_test.php
+++ b/completion/tests/activity_custom_completion_test.php
@@ -113,13 +113,14 @@ class activity_custom_completion_test extends advanced_testcase {
 
         // Mock activity_custom_completion's get_state() method.
         if ($invokecount > 0) {
-            $stub->expects($this->exactly($invokecount))
+            $stateinvocations = $this->exactly($invokecount);
+            $stub->expects($stateinvocations)
                 ->method('get_state')
-                ->withConsecutive(
-                    [$rules[0]],
-                    [$rules[1]]
-                )
-                ->willReturn($rulestates[0], $rulestates[1]);
+                ->willReturnCallback(function ($rule) use ($stateinvocations, $rules, $rulestates) {
+                    $index = $stateinvocations->getInvocationCount() - 1;
+                    $this->assertEquals($rules[$index], $rule);
+                    return $rulestates[$index];
+                });
         } else {
             $stub->expects($this->never())
                 ->method('get_state');
diff --git a/course/tests/category_hooks_test.php b/course/tests/category_hooks_test.php
index 1640e6b4995..a12eb4d92a4 100644
--- a/course/tests/category_hooks_test.php
+++ b/course/tests/category_hooks_test.php
@@ -186,16 +186,21 @@ class category_hooks_test extends \advanced_testcase {
         $mockcategory2 = $this->get_mock_category($category2);
 
         // Define get_plugins_callback_function use in the mock, it is called twice for different callback in the form.
-        $mockcategory2->expects($this->exactly(2))
+        $callbackinvocations = $this->exactly(2);
+        $mockcategory2->expects($callbackinvocations)
             ->method('get_plugins_callback_function')
-            ->withConsecutive(
-                [$this->equalTo('can_course_category_delete')],
-                [$this->equalTo('get_course_category_contents')]
-            )
-            ->willReturn(
-                ['tool_unittest_can_course_category_delete'],
-                ['tool_unittest_get_course_category_contents']
-            );
+            ->willReturnCallback(function ($method) use ($callbackinvocations): array {
+                switch ($callbackinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals('can_course_category_delete', $method);
+                        return ['tool_unittest_can_course_category_delete'];
+                    case 2:
+                        $this->assertEquals('get_course_category_contents', $method);
+                        return ['tool_unittest_get_course_category_contents'];
+                    default:
+                        $this->fail('Unexpected callback invocation');
+                }
+            });
 
         // Now configure fixture to return string for the callback.
         $content = 'Bunch of test artefacts';
diff --git a/files/tests/converter_test.php b/files/tests/converter_test.php
index 94f6209f882..1e866c6762a 100644
--- a/files/tests/converter_test.php
+++ b/files/tests/converter_test.php
@@ -547,11 +547,14 @@ class converter_test extends advanced_testcase {
             ]);
 
         $converter->method('get_document_converter_classes')->willReturn([]);
-        $converter->method('get_next_converter')
-            ->will($this->onConsecutiveCalls(
-                get_class($converterinstance),
-                get_class($converterinstance2)
-            ));
+        $getinvocations = $this->any();
+        $converter
+            ->expects($getinvocations)
+            ->method('get_next_converter')
+            ->willReturnCallback(fn (): string => match ($getinvocations->getInvocationCount()) {
+                1 => get_class($converterinstance),
+                default => get_class($converterinstance2),
+            });
 
         $file = $this->create_stored_file('example content', 'example', [
                 'mimetype' => null,
@@ -566,25 +569,31 @@ class converter_test extends advanced_testcase {
         $conversion->set('status', conversion::STATUS_PENDING);
         $conversion->create();
 
-        $conversion->method('get_status')
-            ->will($this->onConsecutiveCalls(
+        $statusinvocations = $this->atLeast(4);
+        $conversion
+            ->expects($statusinvocations)
+            ->method('get_status')
+            ->willReturnCallback(fn (): int => match ($statusinvocations->getInvocationCount()) {
                 // Initial status check.
-                conversion::STATUS_PENDING,
+                1 => conversion::STATUS_PENDING,
                 // Second check to make sure it's still pending after polling.
-                conversion::STATUS_PENDING,
+                2 => conversion::STATUS_PENDING,
                 // First one fails.
-                conversion::STATUS_FAILED,
+                3 => conversion::STATUS_FAILED,
                 // Second one succeeds.
-                conversion::STATUS_COMPLETE,
+                4 => conversion::STATUS_COMPLETE,
                 // And the final result checked in this unit test.
-                conversion::STATUS_COMPLETE
-            ));
+                default => conversion::STATUS_COMPLETE,
+            });
 
-        $conversion->method('get_converter_instance')
-            ->will($this->onConsecutiveCalls(
-                $converterinstance,
-                $converterinstance2
-            ));
+        $instanceinvocations = $this->any();
+        $conversion
+            ->expects($instanceinvocations)
+            ->method('get_converter_instance')
+            ->willReturnCallback(fn (): object => match ($instanceinvocations->getInvocationCount()) {
+                1 => $converterinstance,
+                default => $converterinstance2,
+            });
 
         $converterinstance->expects($this->once())
             ->method('start_document_conversion');
diff --git a/lib/filestorage/tests/file_system_test.php b/lib/filestorage/tests/file_system_test.php
index a6db36d485d..ecef8f13020 100644
--- a/lib/filestorage/tests/file_system_test.php
+++ b/lib/filestorage/tests/file_system_test.php
@@ -1218,7 +1218,14 @@ class file_system_test extends \advanced_testcase {
         ]);
 
         $fs->method('is_file_readable_locally_by_hash')->willReturn(false);
-        $fs->method('get_local_path_from_hash')->will($this->onConsecutiveCalls('/path/to/remote/file', $filepath));
+        $getinvocations = $this->exactly(2);
+        $fs
+            ->expects($getinvocations)
+            ->method('get_local_path_from_hash')
+            ->willReturnCallback(fn () => match ($getinvocations->getInvocationCount()) {
+                1 => '/path/to/remote/file',
+                2 => $filepath,
+            });
 
         $file = $this->get_stored_file('example content');
 
diff --git a/lib/tests/completionlib_test.php b/lib/tests/completionlib_test.php
index 77cf230f4c8..6972f59e803 100644
--- a/lib/tests/completionlib_test.php
+++ b/lib/tests/completionlib_test.php
@@ -247,10 +247,17 @@ class completionlib_test extends advanced_testcase {
         $c->expects($this->exactly(1)) // Pretend the user has the required capability for overriding completion statuses.
             ->method('user_can_override_completion')
             ->will($this->returnValue(true));
-        $c->expects($this->exactly(2))
+        $getinvocations = $this->exactly(2);
+        $c->expects($getinvocations)
             ->method('get_data')
             ->with($cm, false, 100)
-            ->willReturnOnConsecutiveCalls($current1, $current2);
+            ->willReturnCallback(function () use ($getinvocations, $current1, $current2) {
+                return match ($getinvocations->getInvocationCount()) {
+                    1 => $current1,
+                    2 => $current2,
+                    default => $this->fail('Unexpected invocation count'),
+                };
+            });
         $changed1 = clone($current1);
         $changed1->timemodified = time();
         $changed1->completionstate = COMPLETION_COMPLETE;
@@ -263,12 +270,28 @@ class completionlib_test extends advanced_testcase {
         $changed2->completionstate = COMPLETION_INCOMPLETE;
         $comparewith2 = new phpunit_constraint_object_is_equal_with_exceptions($changed2);
         $comparewith2->add_exception('timemodified', 'assertGreaterThanOrEqual');
-        $c->expects($this->exactly(2))
+        $setinvocations = $this->exactly(2);
+        $c->expects($setinvocations)
             ->method('internal_set_data')
-            ->withConsecutive(
-                array($cm, $comparewith1),
-                array($cm, $comparewith2)
-            );
+            ->willReturnCallback(function ($comparecm, $comparewith) use (
+                $setinvocations,
+                $cm,
+                $comparewith1,
+                $comparewith2
+            ): void {
+                switch ($setinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals($cm, $comparecm);
+                        $comparewith1->evaluate($comparewith);
+                        break;
+                    case 2:
+                        $this->assertEquals($cm, $comparecm);
+                        $comparewith2->evaluate($comparewith);
+                        break;
+                    default:
+                        $this->fail('Unexpected invocation count');
+                }
+            });
         $c->update_state($cm, COMPLETION_COMPLETE, 100, true);
         // And confirm that the status can be changed back to incomplete without an override.
         $c->update_state($cm, COMPLETION_INCOMPLETE, 100);
@@ -685,13 +708,26 @@ class completionlib_test extends advanced_testcase {
             (object)array('id' => 100, 'firstname' => 'Woot', 'lastname' => 'Plugh'),
             (object)array('id' => 201, 'firstname' => 'Vroom', 'lastname' => 'Xyzzy'))));
 
-        $c->expects($this->exactly(3))
+        $updateinvocations = $this->exactly(3);
+        $c->expects($updateinvocations)
             ->method('update_state')
-            ->withConsecutive(
-                array($cm, COMPLETION_UNKNOWN, 100),
-                array($cm, COMPLETION_UNKNOWN, 101),
-                array($cm, COMPLETION_UNKNOWN, 201)
-            );
+            ->willReturnCallback(function ($comparecm, $state, $userid) use ($updateinvocations, $cm): void {
+                $this->assertEquals($cm, $comparecm);
+                $this->assertEquals(COMPLETION_UNKNOWN, $state);
+                switch ($updateinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals(100, $userid);
+                        break;
+                    case 2:
+                        $this->assertEquals(101, $userid);
+                        break;
+                    case 3:
+                        $this->assertEquals(201, $userid);
+                        break;
+                    default:
+                        $this->fail('Unexpected invocation count');
+                }
+            });
 
         $c->reset_all_state($cm);
     }
@@ -1178,20 +1214,31 @@ class completionlib_test extends advanced_testcase {
             ->method('get_tracked_users')
             ->with(true,  3,  0,  '',  '',  '',  null)
             ->will($this->returnValue($tracked));
-        $DB->expects($this->exactly(2))
+        $inorequalsinvocations = $this->exactly(2);
+        $DB->expects($inorequalsinvocations)
             ->method('get_in_or_equal')
-            ->withConsecutive(
-                array(array_slice($ids, 0, 1000)),
-                array(array_slice($ids, 1000))
-            )
-            ->willReturnOnConsecutiveCalls(
-                array(' IN whatever', array()),
-                array(' IN whatever2', array()));
-        $DB->expects($this->exactly(2))
+            ->willReturnCallback(function ($paramids) use ($inorequalsinvocations, $ids) {
+                switch ($inorequalsinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals(array_slice($ids, 0, 1000), $paramids);
+                        return [' IN whatever', []];
+                    case 2:
+                        $this->assertEquals(array_slice($ids, 1000), $paramids);
+                        return [' IN whatever2', []];
+                    default:
+                        $this->fail('Unexpected invocation count');
+                }
+            });
+        $getinvocations = $this->exactly(2);
+        $DB->expects($getinvocations)
             ->method('get_recordset_sql')
-            ->willReturnOnConsecutiveCalls(
-                new core_completionlib_fake_recordset(array_slice($progress, 0, 1000)),
-                new core_completionlib_fake_recordset(array_slice($progress, 1000)));
+            ->willReturnCallback(function () use ($getinvocations, $progress) {
+                return match ($getinvocations->getInvocationCount()) {
+                    1 => new core_completionlib_fake_recordset(array_slice($progress, 0, 1000)),
+                    2 => new core_completionlib_fake_recordset(array_slice($progress, 1000)),
+                    default => $this->fail('Unexpected invocation count'),
+                };
+            });
 
         $result = $c->get_progress_all(true, 3);
         $resultok = true;
diff --git a/lib/tests/content/export/exportable_items/exportable_filearea_test.php b/lib/tests/content/export/exportable_items/exportable_filearea_test.php
index d0a71ca124c..f49ca5bedb4 100644
--- a/lib/tests/content/export/exportable_items/exportable_filearea_test.php
+++ b/lib/tests/content/export/exportable_items/exportable_filearea_test.php
@@ -234,15 +234,18 @@ class exportable_filearea_test extends advanced_testcase {
             $filepathinzip = $subdir . '/' . $file->get_filearea() . '/' . $file->get_filepath() . $file->get_filename();
             $filepathinzip = ltrim(preg_replace('#/+#', '/', $filepathinzip), '/');
             $storedfileargs[] = [
-                $this->equalTo($context),
-                $this->equalTo($filepathinzip),
-                $this->equalTo($file),
+                $context,
+                $filepathinzip,
+                $file,
             ];
         }
 
-        $archive->expects($this->exactly(count($expectedfiles)))
+        $invocations = $this->exactly(count($expectedfiles));
+        $archive->expects($invocations)
             ->method('add_file_from_stored_file')
-            ->withConsecutive(...$storedfileargs);
+            ->willReturnCallback(function (...$args) use ($invocations, $storedfileargs) {
+                $this->assertEquals($storedfileargs[$invocations->getInvocationCount() - 1], $args);
+            });
 
         return $exportable->add_to_archive($archive);
     }
diff --git a/lib/tests/content/export/exportable_items/exportable_textarea_test.php b/lib/tests/content/export/exportable_items/exportable_textarea_test.php
index e3594acf5cc..7a695d6e50c 100644
--- a/lib/tests/content/export/exportable_items/exportable_textarea_test.php
+++ b/lib/tests/content/export/exportable_items/exportable_textarea_test.php
@@ -320,15 +320,18 @@ EOF;
             $filepathinzip = dirname($subdir) . $file->get_filearea() . '/' . $file->get_filepath() . $file->get_filename();
             $filepathinzip = ltrim(preg_replace('#/+#', '/', $filepathinzip), '/');
             $storedfileargs[] = [
-                $this->equalTo($context),
-                $this->equalTo($filepathinzip),
-                $this->equalTo($file),
+                $context,
+                $filepathinzip,
+                $file,
             ];
         }
 
-        $archive->expects($this->exactly(count($expectedfiles)))
+        $invocations = $this->exactly(count($expectedfiles));
+        $archive->expects($invocations)
             ->method('add_file_from_stored_file')
-            ->withConsecutive(...$storedfileargs);
+            ->willReturnCallback(function (...$args) use ($invocations, $storedfileargs) {
+                $this->assertEquals($storedfileargs[$invocations->getInvocationCount() - 1], $args);
+            });
 
         $archive->expects($this->never())
             ->method('add_file_from_string');
diff --git a/repository/dropbox/tests/api_test.php b/repository/dropbox/tests/api_test.php
index 6aec039ce13..7ee672e207b 100644
--- a/repository/dropbox/tests/api_test.php
+++ b/repository/dropbox/tests/api_test.php
@@ -403,25 +403,37 @@ class api_test extends \advanced_testcase {
 
         $endpoint = 'testEndpoint';
 
-        // We can't detect if fetch_dropbox_data was called twice because
-        // we can'
-        $mock->expects($this->exactly(3))
+        $requestinvocations = $this->exactly(3);
+        $mock->expects($requestinvocations)
             ->method('request')
-            ->will($this->onConsecutiveCalls(
-                json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['foo', 'bar']]),
-                json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['baz']]),
-                json_encode(['has_more' => false, 'cursor' => '', 'matches' => ['bum']])
-            ));
+            ->willReturnCallback(function () use ($requestinvocations): string {
+                return match ($requestinvocations->getInvocationCount()) {
+                    1 => json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['foo', 'bar']]),
+                    2 => json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['baz']]),
+                    3 => json_encode(['has_more' => false, 'cursor' => '', 'matches' => ['bum']]),
+                    default => $this->fail('Unexpected call to the call() method.'),
+                };
+            });
 
         // We automatically adjust for the /continue endpoint.
-        $mock->expects($this->exactly(3))
+        $apiinvocations = $this->exactly(3);
+        $mock->expects($apiinvocations)
             ->method('get_api_endpoint')
-            ->withConsecutive(['testEndpoint'], ['testEndpoint/continue'], ['testEndpoint/continue'])
-            ->willReturn($this->onConsecutiveCalls(
-                'https://example.com/api/2/testEndpoint',
-                'https://example.com/api/2/testEndpoint/continue',
-                'https://example.com/api/2/testEndpoint/continue'
-            ));
+            ->willReturnCallback(function ($endpoint) use ($apiinvocations): string {
+                switch ($apiinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals('testEndpoint', $endpoint);
+                        return 'https://example.com/api/2/testEndpoint';
+                    case 2:
+                        $this->assertEquals('testEndpoint/continue', $endpoint);
+                        return 'https://example.com/api/2/testEndpoint/continue';
+                    case 3:
+                        $this->assertEquals('testEndpoint/continue', $endpoint);
+                        return 'https://example.com/api/2/testEndpoint/continue';
+                    default:
+                        $this->fail('Unexpected call to the get_api_endpoint() method.');
+                }
+            });
 
         // Make the call.
         $rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
@@ -468,12 +480,21 @@ class api_test extends \advanced_testcase {
         $mock->expects($this->never())
             ->method('get_api_endpoint');
 
-        $mock->expects($this->exactly(2))
+        $headerinvocations = $this->exactly(2);
+        $mock->expects($headerinvocations)
             ->method('setHeader')
-            ->withConsecutive(
-                [$this->equalTo('Content-Type: ')],
-                [$this->equalTo('Dropbox-API-Arg: ' . json_encode($data))]
-            );
+            ->willReturnCallback(function ($header) use ($data, $headerinvocations): void {
+                switch ($headerinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals('Content-Type: ', $header);
+                        break;
+                    case 2:
+                        $this->assertEquals('Dropbox-API-Arg: ' . json_encode($data), $header);
+                        break;
+                    default:
+                        $this->fail('Unexpected call to the setHeader() method.');
+                }
+            });
 
         // Only one request should be made, and it should forcibly be a POST.
         $mock->expects($this->once())
@@ -546,21 +567,23 @@ class api_test extends \advanced_testcase {
         $sharelink = 'https://example.com/share/link';
 
         // Mock fetch_dropbox_data to return an existing file.
-        $mock->expects($this->exactly(2))
+        $fetchinvocations = $this->exactly(2);
+        $mock->expects($fetchinvocations)
             ->method('fetch_dropbox_data')
-            ->withConsecutive(
-                [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
-                [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
-                    'path' => $id,
-                    'settings' => [
-                        'requested_visibility' => 'public',
-                    ]
-                ])]
-            )
-            ->will($this->onConsecutiveCalls(
-                (object) ['links' => []],
-                $file
-            ));
+            ->willReturnCallback(function ($path, $values) use ($fetchinvocations, $id, $file): object {
+                switch ($fetchinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals('sharing/list_shared_links', $path);
+                        $this->assertEquals(['path' => $id], $values);
+                        return (object) ['links' => []];
+                    case 2:
+                        $this->assertEquals('sharing/create_shared_link_with_settings', $path);
+                        $this->assertEquals(['path' => $id, 'settings' => ['requested_visibility' => 'public']], $values);
+                        return $file;
+                    default:
+                        $this->fail('Unexpected call to the fetch_dropbox_data() method.');
+                }
+            });
 
         $mock->expects($this->once())
             ->method('normalize_file_share_info')
@@ -587,19 +610,18 @@ class api_test extends \advanced_testcase {
         // Mock fetch_dropbox_data to return an existing file.
         $mock->expects($this->exactly(2))
             ->method('fetch_dropbox_data')
-            ->withConsecutive(
-                [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
-                [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
-                    'path' => $id,
-                    'settings' => [
-                        'requested_visibility' => 'public',
-                    ]
-                ])]
-            )
-            ->will($this->onConsecutiveCalls(
-                (object) ['links' => []],
-                null
-            ));
+            ->willReturnCallback(function ($path, $values) use ($id): ?object {
+                switch ($path) {
+                    case 'sharing/list_shared_links':
+                        $this->assertEquals(['path' => $id], $values);
+                        return (object) ['links' => []];
+                    case 'sharing/create_shared_link_with_settings':
+                        $this->assertEquals(['path' => $id, 'settings' => ['requested_visibility' => 'public']], $values);
+                        return null;
+                    default:
+                        $this->fail('Unexpected call to the fetch_dropbox_data() method.');
+                }
+            });
 
         $mock->expects($this->never())
             ->method('normalize_file_share_info');
diff --git a/repository/googledocs/tests/googledocs_search_content_test.php b/repository/googledocs/tests/googledocs_search_content_test.php
index e3d807f452f..c39b23abc5c 100644
--- a/repository/googledocs/tests/googledocs_search_content_test.php
+++ b/repository/googledocs/tests/googledocs_search_content_test.php
@@ -64,28 +64,34 @@ class googledocs_search_content_test extends \googledocs_content_testcase {
         // instance it is being called to fetch the shared drives (shared_drives_list), while in the second instance
         // to fetch the relevant drive contents (list) that match the search criteria. Also, define the returned
         // data objects by these calls.
-        $servicemock->expects($this->exactly(2))
+        $callinvocations = $this->exactly(2);
+        $servicemock->expects($callinvocations)
             ->method('call')
-            ->withConsecutive(
-                [
-                    'shared_drives_list',
-                    [],
-                ],
-                [
-                    'list',
-                    $searchparams,
-                ]
-            )
-            ->willReturnOnConsecutiveCalls(
-                (object)[
-                    'kind' => 'drive#driveList',
-                    'nextPageToken' => 'd838181f30b0f5',
-                    'drives' => $shareddrives,
-                ],
-                (object)[
-                    'files' => $searccontents,
-                ]
-            );
+            ->willReturnCallback(function(string $method, array $params) use (
+                $callinvocations,
+                $shareddrives,
+                $searccontents,
+                $searchparams,
+            ) {
+                switch ($callinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals('shared_drives_list', $method);
+
+                        $this->assertEmpty($params);
+                        return (object) [
+                            'kind' => 'drive#driveList',
+                            'nextPageToken' => 'd838181f30b0f5',
+                            'drives' => $shareddrives,
+                        ];
+                    case 2:
+                        $this->assertEquals('list', $method);
+                        $this->assertEquals($searchparams, $params);
+
+                        return (object) [
+                            'files' => $searccontents,
+                        ];
+                }
+            });
 
         // Construct the node path.
         $path = \repository_googledocs::REPOSITORY_ROOT_ID . '|' . urlencode('Google Drive') . '/' .
diff --git a/repository/googledocs/tests/local/browser/googledocs_drive_content_test.php b/repository/googledocs/tests/local/browser/googledocs_drive_content_test.php
index a5aa162009f..6a84a2a4994 100644
--- a/repository/googledocs/tests/local/browser/googledocs_drive_content_test.php
+++ b/repository/googledocs/tests/local/browser/googledocs_drive_content_test.php
@@ -63,28 +63,35 @@ class googledocs_drive_content_test extends \googledocs_content_testcase {
         // Assert that the call() method is being called twice with the given arguments consecutively. In the first
         // instance it is being called to fetch the shared drives (shared_drives_list), while in the second instance
         // to fetch the relevant drive contents (list). Also, define the returned data objects by these calls.
-        $servicemock->expects($this->exactly(2))
+        $callinvocations = $this->exactly(2);
+        $servicemock->expects($callinvocations)
             ->method('call')
-            ->withConsecutive(
-                [
-                    'shared_drives_list',
-                    [],
-                ],
-                [
-                    'list',
-                    $listparams,
-                ]
-            )
-            ->willReturnOnConsecutiveCalls(
-                (object)[
-                    'kind' => 'drive#driveList',
-                    'nextPageToken' => 'd838181f30b0f5',
-                    'drives' => $shareddrives,
-                ],
-                (object)[
-                    'files' => $drivecontents,
-                ]
-            );
+            ->willReturnCallback(function(string $method, array $params) use (
+                $callinvocations,
+                $shareddrives,
+                $listparams,
+                $drivecontents,
+            ) {
+                switch ($callinvocations->getInvocationCount()) {
+                    case 1:
+                        $this->assertEquals('shared_drives_list', $method);
+                        $this->assertEquals([], $params);
+
+                        return (object) [
+                            'kind' => 'drive#driveList',
+                            'nextPageToken' => 'd838181f30b0f5',
+                            'drives' => $shareddrives,
+                        ];
+                    case 2:
+                        $this->assertEquals('list', $method);
+                        $this->assertEquals($listparams, $params);
+                        return (object)[
+                            'files' => $drivecontents,
+                        ];
+                    default:
+                        $this->fail('Unexpected call to the call() method.');
+                }
+            });
 
         // Set the disallowed file types (extensions).
         $this->disallowedextensions = $filterextensions;