diff --git a/typo3/sysext/recycler/Tests/Functional/Task/Pages/CleanerTaskTest.php b/typo3/sysext/recycler/Tests/Functional/Task/Pages/CleanerTaskTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cebb28f36deb185c109dc4fc1dcb26f02c26acf9
--- /dev/null
+++ b/typo3/sysext/recycler/Tests/Functional/Task/Pages/CleanerTaskTest.php
@@ -0,0 +1,75 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Recycler\Tests\Functional\Task\Pages;
+
+use TYPO3\CMS\Recycler\Task\CleanerTask;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+/**
+ * Test Case
+ */
+class CleanerTaskTest extends FunctionalTestCase
+{
+    protected $coreExtensionsToLoad = ['recycler', 'scheduler'];
+
+    /**
+     * @test
+     */
+    public function taskRemovesDeletedPages()
+    {
+        $this->importCSVDataSet(__DIR__ . '/DataSet/Fixtures/pages.csv');
+        $subject = new CleanerTask();
+        $subject->setTcaTables(['pages']);
+        $result = $subject->execute();
+        $this->assertCSVDataSet('typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted.csv');
+        self::assertTrue($result);
+    }
+
+    /**
+     * @test
+     */
+    public function taskRemovesOnlyPagesLongerDeletedThanPeriod()
+    {
+        $this->importCSVDataSet(__DIR__ . '/DataSet/Fixtures/pages.csv');
+        $subject = new CleanerTask();
+        $subject->setTcaTables(['pages']);
+
+        // this is when the test was created. One of the fixtures (uid 4) has this date
+        $creationDate = date_create_immutable_from_format('d.m.Y', '28.09.2020')->setTime(0, 0, 0);
+        // we want to set the period in a way that older records get deleted, but not the one created today
+        $difference = $creationDate->diff(new \DateTime(), true);
+        // let's set the amount of days one higher than the reference date
+        $period = (int)$difference->format('%d') + 1;
+        $subject->setPeriod($period);
+        $result = $subject->execute();
+        $this->assertCSVDataSet('typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted_with_period.csv');
+        self::assertTrue($result);
+    }
+
+    /**
+     * @test
+     */
+    public function taskFailsOnError()
+    {
+        $subject = new CleanerTask();
+        $GLOBALS['TCA']['not_existing_table']['ctrl']['delete'] = 'deleted';
+        $subject->setTcaTables(['not_existing_table']);
+        $result = $subject->execute();
+        self::assertFalse($result);
+    }
+}
diff --git a/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted.csv b/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted.csv
new file mode 100644
index 0000000000000000000000000000000000000000..075d6d1b2f94b1262f00219381aea0a2a5c86afb
--- /dev/null
+++ b/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted.csv
@@ -0,0 +1,4 @@
+"pages",,,,
+,"uid","pid","title","deleted"
+,1,0,"root",0
+,2,1,"Page 1",0
diff --git a/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted_with_period.csv b/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted_with_period.csv
new file mode 100644
index 0000000000000000000000000000000000000000..f7f6307add8b7ae774e4f2de82da7dcbb7ccfb9b
--- /dev/null
+++ b/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Assertion/pages_deleted_with_period.csv
@@ -0,0 +1,5 @@
+"pages",,,,,
+,"uid","pid","title","deleted","tstamp"
+,1,0,"root",0,0
+,2,1,"Page 1",0,0
+,4,1,"Page 3",1,1601251200
diff --git a/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Fixtures/pages.csv b/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Fixtures/pages.csv
new file mode 100644
index 0000000000000000000000000000000000000000..64fd98e145465b00340de6311740f8c088bf8b06
--- /dev/null
+++ b/typo3/sysext/recycler/Tests/Functional/Task/Pages/DataSet/Fixtures/pages.csv
@@ -0,0 +1,6 @@
+"pages",,,,,
+,"uid","pid","title","deleted","tstamp"
+,1,0,"root",0,0
+,2,1,"Page 1",0,0
+,3,1,"Page 2",1,1598572800
+,4,1,"Page 3",1,1601251200
diff --git a/typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php b/typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php
deleted file mode 100644
index 74d90a417a3d5d7965a050dcb4fad54ded5afc67..0000000000000000000000000000000000000000
--- a/typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-namespace TYPO3\CMS\Recycler\Tests\Unit\Task;
-
-use Doctrine\DBAL\DBALException;
-use Doctrine\DBAL\Driver\Statement;
-use Prophecy\Argument;
-use Prophecy\Prophecy\ObjectProphecy;
-use TYPO3\CMS\Core\Database\Connection;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
-use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Recycler\Task\CleanerTask;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Testcase
- */
-class CleanerTaskTest extends UnitTestCase
-{
-    /**
-     * @var \PHPUnit\Framework\MockObject\MockObject|CleanerTask
-     */
-    protected $subject;
-
-    /**
-     * sets up an instance of \TYPO3\CMS\Recycler\Task\CleanerTask
-     */
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->subject = $this->getMockBuilder(CleanerTask::class)
-            ->setMethods(['dummy'])
-            ->disableOriginalConstructor()
-            ->getMock();
-    }
-
-    /**
-     * @test
-     */
-    public function getPeriodCanBeSet()
-    {
-        $period = 14;
-        $this->subject->setPeriod($period);
-
-        self::assertEquals($period, $this->subject->getPeriod());
-    }
-
-    /**
-     * @test
-     */
-    public function getTcaTablesCanBeSet()
-    {
-        $tables = ['pages', 'tt_content'];
-        $this->subject->setTcaTables($tables);
-
-        self::assertEquals($tables, $this->subject->getTcaTables());
-    }
-
-    /**
-     * @test
-     */
-    public function taskBuildsCorrectQuery()
-    {
-        $GLOBALS['TCA']['pages']['ctrl']['delete'] = 'deleted';
-        $GLOBALS['TCA']['pages']['ctrl']['tstamp'] = 'tstamp';
-
-        /** @var \PHPUnit\Framework\MockObject\MockObject|CleanerTask $subject */
-        $subject = $this->getMockBuilder(CleanerTask::class)
-            ->setMethods(['getPeriodAsTimestamp'])
-            ->disableOriginalConstructor()
-            ->getMock();
-        $subject->setTcaTables(['pages']);
-        $subject->expects(self::once())->method('getPeriodAsTimestamp')->willReturn(400);
-
-        /** @var Connection|ObjectProphecy $connection */
-        $connection = $this->prophesize(Connection::class);
-        $connection->getDatabasePlatform()->willReturn(new MockPlatform());
-        $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
-        $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
-
-        // TODO: This should rather be a functional test if we need a query builder
-        // or we should clean up the code itself to not need to mock internal behavior here
-
-        $statementProphet = $this->prophesize(Statement::class);
-
-        $restrictionProphet = $this->prophesize(DefaultRestrictionContainer::class);
-        $restrictionProphet->removeAll()->willReturn($restrictionProphet->reveal());
-
-        $queryBuilderProphet = $this->prophesize(QueryBuilder::class);
-        $queryBuilderProphet->expr()->willReturn(
-            GeneralUtility::makeInstance(ExpressionBuilder::class, $connection->reveal())
-        );
-        $queryBuilderProphet->getRestrictions()->willReturn($restrictionProphet->reveal());
-        $queryBuilderProphet->createNamedParameter(Argument::cetera())->willReturnArgument(0);
-        $queryBuilderProphet->delete(Argument::cetera())->willReturn($queryBuilderProphet->reveal());
-        $queryBuilderProphet->where(Argument::cetera())->willReturn($queryBuilderProphet->reveal());
-        $queryBuilderProphet->execute()->willReturn($statementProphet->reveal());
-
-        $connectionPool = $this->prophesize(ConnectionPool::class);
-        $connectionPool->getQueryBuilderForTable('pages')->willReturn($queryBuilderProphet->reveal());
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPool->reveal());
-
-        self::assertTrue($subject->execute());
-    }
-
-    /**
-     * @test
-     */
-    public function taskFailsOnError()
-    {
-        $GLOBALS['TCA']['pages']['ctrl']['delete'] = 'deleted';
-        $GLOBALS['TCA']['pages']['ctrl']['tstamp'] = 'tstamp';
-
-        $this->subject->setTcaTables(['pages']);
-
-        /** @var Connection|ObjectProphecy $connection */
-        $connection = $this->prophesize(Connection::class);
-        $connection->getDatabasePlatform()->willReturn(new MockPlatform());
-        $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
-        $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
-
-        // TODO: This should rather be a functional test if we need a query builder
-        // or we should clean up the code itself to not need to mock internal behavior here
-        $queryBuilder = new QueryBuilder(
-            $connection->reveal(),
-            null,
-            new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
-        );
-
-        $connectionPool = $this->prophesize(ConnectionPool::class);
-        $connectionPool->getQueryBuilderForTable('pages')->willReturn($queryBuilder);
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPool->reveal());
-
-        $connection->executeUpdate(Argument::cetera())
-            ->shouldBeCalled()
-            ->willThrow(new DBALException('testing', 1476122315));
-
-        self::assertFalse($this->subject->execute());
-    }
-}