-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Parallel Testing Mongodb #3268
-
DatabaseMigration is way too slow for testing takes 3 to 5 seconds for each test, RefreshDatabase wont work if you test a function with transaction since nested transaction is not allowed. Looking at the test technique made by @GromNaN in this package, truncate is being used to make testing faster which I also tried and tested and shave off the time to average 0.2 seconds to execute each test. What could be the reason why DatabaseMigration takes a long time to test?
Beta Was this translation helpful? Give feedback.
All reactions
I made parallel testing work by providing custom setUpTestCase to create parallel multiple database, which in default it only creates when testcase uses one of this traits,
When testing mongodb, any of this trait doesnt play nice, refreshdatabase you cannot test when controller that has transaction because it disallows nested transaction, truncation foreign key error, transaction same with refreshdatabase issu, DatabaseMigrations 5x slower than without any trait
$databaseTraits = [ Testing\DatabaseMigrations::class, Testing\DatabaseTransactions::class, Testing\DatabaseTruncation::class, Testing\RefreshDatabase::class, ...
Replies: 3 comments 6 replies
-
You're right that deleting/creating a database takes longer than simply deleting all the documents in the collections. However, you still need to consider the insertion of fixtures between each test. Perhaps you could optimize this by dumping/importing with bulkWrite instead of unit insertions.
Another common technique on advanced databases is to use a transaction that is reverted at the end of each test. This also works with MongoDB.
In setUp
:
$connection->beginTransaction();
In tearDown
:
$connection->rollback();
Beta Was this translation helpful? Give feedback.
All reactions
-
I just use hooks beforeEach and afterEach, this requires to monitor what models needs to be truncated some factories have relations too which also being auto created. What could be the command to nuke the database or is there any way to scan all models that is being extended by MongoDB\Laravel\Eloquent\Model and truncate each of them?
beforeEach(function () { $this->seed(DefaultDeviceSeeder::class); }); afterEach(function () { User::truncate(); });
Beta Was this translation helpful? Give feedback.
All reactions
-
You don't need to know about the models, you can list all the collections of your database and delete all the documents they contain.
$database = DB::connection('mongodb')->getMongoDB(); foreach($database->listCollectionNames() as $collectionName) { $database->getCollection($collectionName)->deleteMany([]); }
Don't drop the collections if you use indexes or specific collections options like timeseries.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
You don't need to know about the models, you can list all the collections of your database and delete all the documents they contain.
$database = DB::connection('mongodb')->getMongoDB(); foreach($database->listCollectionNames() as $collectionName) { $database->getCollection($collectionName)->deleteMany([]); }Don't drop the collections if you use indexes or specific collections options like timeseries.
Hahaha you are right this is better solution
Beta Was this translation helpful? Give feedback.
All reactions
-
You don't need to know about the models, you can list all the collections of your database and delete all the documents they contain.
$database = DB::connection('mongodb')->getMongoDB(); foreach($database->listCollectionNames() as $collectionName) { $database->getCollection($collectionName)->deleteMany([]); }
Don't drop the collections if you use indexes or specific collections options like timeseries.
getCollection is undefined
I replace it with this
DB::table($collectionName)->truncate();
Beta Was this translation helpful? Give feedback.
All reactions
-
Just additional feedback it might be the fastest to delete collections or truncate but when you try to do parallel testing you might need DatabaseMigration or RefreshDatabase for it to work and make laravel auto create additional databases. But even if you parallel test, the sequential test for truncate is still faster.
Truncate strategy sequential, doesnt work with parallel test
Tests: 19 passed (21 assertions)
Duration: 2.52s
RefreshDatabase sequential(this works as long as you are testing doesn't do transactions, since by itself its transaction which mongodb doesnt allow nested one)
Tests: 19 passed (21 assertions)
Duration: 5.03s
RefreshDatabase parallel
sail artisan test --parallel --processes=4
...................
Tests: 19 passed (21 assertions)
Duration: 5.58s
Parallel: 4 processes
DatabaseMigration sequential
Tests: 19 passed (21 assertions)
Duration: 29.14s
DatabaseMigration parallel
sail artisan test --parallel --processes=4
...................
Tests: 19 passed (21 assertions)
Duration: 24.90s
Parallel: 4 processes
Beta Was this translation helpful? Give feedback.
All reactions
-
I will revisit this result once I written more assertions and test, and retest the parallel if it drastically improves if there are more test.
Beta Was this translation helpful? Give feedback.
All reactions
-
https://laravel.com/docs/11.x/dusk#reset-truncation
DatabaseTruncation trait exist but its not supported by mongodb
Method MongoDB\Laravel\Schema\Grammar::compileDisableForeignKeyConstraints does not exist. at vendor/laravel/framework/src/Illuminate/Macroable/Traits/Macroable.php:115 111▕ */ 112▕ public function __call($method, $parameters) 113▕ { 114▕ if (! static::hasMacro($method)) { ➜ 115▕ throw new BadMethodCallException(sprintf( 116▕ 'Method %s::%s does not exist.', static::class, $method 117▕ )); 118▕ } 119▕
Beta Was this translation helpful? Give feedback.
All reactions
-
I made parallel testing work by providing custom setUpTestCase to create parallel multiple database, which in default it only creates when testcase uses one of this traits,
When testing mongodb, any of this trait doesnt play nice, refreshdatabase you cannot test when controller that has transaction because it disallows nested transaction, truncation foreign key error, transaction same with refreshdatabase issu, DatabaseMigrations 5x slower than without any trait
$databaseTraits = [ Testing\DatabaseMigrations::class, Testing\DatabaseTransactions::class, Testing\DatabaseTruncation::class, Testing\RefreshDatabase::class, ];
This doesnt require any traits, and handles dropping database, in mongodb it auto creates database once there is a new write. This is the fastest method of testing and shaves off duration
private function customParallelTestSetup(): void { if (config('app.env') === 'testing') { ParallelTesting::setUpTestCase(function (TestCase $testCase) { $default = config('database.default'); $database = DB::getConfig('database'); $token = ParallelTesting::token(); $testDatabase = "{$database}_test_{$token}"; config()->set( "database.connections.{$default}.database", $testDatabase, ); DB::purge(); ParallelTesting::callSetUpTestDatabaseCallbacks($testDatabase); }); // Executed when a test database is created... ParallelTesting::setUpTestDatabase(function (string $database, int $token) { Artisan::call('db:seed'); }); ParallelTesting::tearDownTestCase(function (int $token, TestCase $testCase) { //Slower but cleans up everything // DB::getDatabase()->drop(); // This cleanup makes my test lowered from 50seconds to 12 seconds foreach (DB::connection('mongodb')->getDatabase()->listCollections() as $collection) { $name = $collection->getName(); $type = $collection->getType(); // Skip system collections if (str_starts_with($name, 'system.')) { continue; } if ($type === 'view') { continue; } DB::table($name)->truncate(); } }); } }
For sequential testing
use beforeEach global hook in pest, then check if Parallel token exist, if it doesnt exist its a sequential test not parallel test then do your cleanup
$token = ParallelTesting::token();
if(!$token)
{
//Cleanup here
}
Beta Was this translation helpful? Give feedback.