diff --git a/composer.json b/composer.json
index 64630833d..81896f8f8 100644
--- a/composer.json
+++ b/composer.json
@@ -44,6 +44,7 @@
         "ssddanbrown/htmldiff": "^1.0.2"
     },
     "require-dev": {
+        "brianium/paratest": "^6.6",
         "fakerphp/faker": "^1.16",
         "itsgoingd/clockwork": "^5.1",
         "mockery/mockery": "^1.4",
@@ -73,6 +74,8 @@
         "format": "phpcbf",
         "lint": "phpcs",
         "test": "phpunit",
+        "t": "@php artisan test --parallel",
+        "t-reset": "@php artisan test --recreate-databases",
         "post-autoload-dump": [
             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
             "@php artisan package:discover --ansi"
diff --git a/readme.md b/readme.md
index d0ae1b4f6..16992341d 100644
--- a/readme.md
+++ b/readme.md
@@ -108,14 +108,9 @@ npm run dev
 
 BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the database name, user name and password all defined as `bookstack-test`. You will have to create that database and that set of credentials before testing.
 
-The testing database will also need migrating and seeding beforehand. This can be done with the following commands:
+The testing database will also need migrating and seeding beforehand. This can be done by running `composer refresh-test-database`.
 
-``` bash
-php artisan migrate --database=mysql_testing
-php artisan db:seed --class=DummyContentSeeder --database=mysql_testing
-```
-
-Once done you can run `composer test` in the application root directory to run all tests.
+Once done you can run `composer test` in the application root directory to run all tests. Tests can be ran in parallel by running them via `composer t`. This will use Laravel's built-in parallel testing functionality, and attempt to create and seed a database instance for each testing thread. If required these parallel testing instances can be reset, before testing again, by running `composer t-reset`.
 
 ### 📜 Code Standards
 
diff --git a/tests/TestCase.php b/tests/TestCase.php
index f17d27a1a..594194168 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -22,10 +22,12 @@ use GuzzleHttp\Client;
 use GuzzleHttp\Handler\MockHandler;
 use GuzzleHttp\HandlerStack;
 use GuzzleHttp\Middleware;
+use Illuminate\Contracts\Console\Kernel;
 use Illuminate\Foundation\Testing\DatabaseTransactions;
 use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Env;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Testing\Assert as PHPUnit;
 use Monolog\Handler\TestHandler;
@@ -47,6 +49,21 @@ abstract class TestCase extends BaseTestCase
      */
     protected string $baseUrl = 'http://localhost';
 
+    /**
+     * Creates the application.
+     *
+     * @return \Illuminate\Foundation\Application
+     */
+    public function createApplication()
+    {
+        /** @var \Illuminate\Foundation\Application  $app */
+        $app = require __DIR__ . '/../bootstrap/app.php';
+        $app->register(TestServiceProvider::class);
+        $app->make(Kernel::class)->bootstrap();
+
+        return $app;
+    }
+
     /**
      * Set the current user context to be an admin.
      */
@@ -299,6 +316,8 @@ abstract class TestCase extends BaseTestCase
     /**
      * Run a set test with the given env variable.
      * Remembers the original and resets the value after test.
+     * Database config is juggled so the value can be restored when
+     * parallel testing are used, where multiple databases exist.
      */
     protected function runWithEnv(string $name, $value, callable $callback)
     {
@@ -311,7 +330,12 @@ abstract class TestCase extends BaseTestCase
             $_SERVER[$name] = $value;
         }
 
+        $database = config('database.connections.mysql_testing.database');
         $this->refreshApplication();
+
+        DB::purge();
+        config()->set('database.connections.mysql_testing.database', $database);
+
         $callback();
 
         if (is_null($originalVal)) {
diff --git a/tests/TestServiceProvider.php b/tests/TestServiceProvider.php
new file mode 100644
index 000000000..9ad48c442
--- /dev/null
+++ b/tests/TestServiceProvider.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Tests;
+
+use Illuminate\Support\Facades\Artisan;
+use Illuminate\Support\Facades\ParallelTesting;
+use Illuminate\Support\ServiceProvider;
+
+class TestServiceProvider extends ServiceProvider
+{
+    /**
+     * Bootstrap services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        // Tell Laravel's parallel testing functionality to seed the test
+        // databases with the DummyContentSeeder upon creation.
+        // This is only done for initial database creation. Seeding
+        // won't occur on every run.
+        ParallelTesting::setUpTestDatabase(function ($database, $token) {
+            Artisan::call('db:seed --class=DummyContentSeeder');
+        });
+    }
+}
diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php
index e83758a95..ac4b35de2 100644
--- a/tests/ThemeTest.php
+++ b/tests/ThemeTest.php
@@ -322,8 +322,8 @@ class ThemeTest extends TestCase
 
     public function test_export_body_start_and_end_template_files_can_be_used()
     {
-        $bodyStartStr = 'barry-fought-against-the-panther';
-        $bodyEndStr = 'barry-lost-his-fight-with-grace';
+        $bodyStartStr = 'garry-fought-against-the-panther';
+        $bodyEndStr = 'garry-lost-his-fight-with-grace';
         /** @var Page $page */
         $page = Page::query()->first();
 
@@ -342,18 +342,18 @@ class ThemeTest extends TestCase
     protected function usingThemeFolder(callable $callback)
     {
         // Create a folder and configure a theme
-        $themeFolderName = 'testing_theme_' . rtrim(base64_encode(time()), '=');
+        $themeFolderName = 'testing_theme_' . str_shuffle(rtrim(base64_encode(time()), '='));
         config()->set('view.theme', $themeFolderName);
         $themeFolderPath = theme_path('');
+
+        // Create theme folder and clean it up on application tear-down
         File::makeDirectory($themeFolderPath);
+        $this->beforeApplicationDestroyed(fn() => File::deleteDirectory($themeFolderPath));
 
         // Run provided callback with theme env option set
         $this->runWithEnv('APP_THEME', $themeFolderName, function () use ($callback, $themeFolderName) {
             call_user_func($callback, $themeFolderName);
         });
-
-        // Cleanup the custom theme folder we created
-        File::deleteDirectory($themeFolderPath);
     }
 }