-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DB::afterCommit
callbacks aren't run in tests when RefreshDatabase
trait is used
#35857
Comments
DB::afterCommit
callbacks aren't run when RefreshDatabase
trait is usedDB::afterCommit
callbacks aren't run in tests when RefreshDatabase
trait is used
I've installed your repo locally and both tests pass for me. Can you first please try one of the support channels below? If you can actually identify this as a bug, feel free to report back and I'll gladly help you out and re-open this issue. Thanks! |
@driesvints have you used MySQL or Postgres driver? with SQLite runs because I think it doesn't run transactions |
Ah, you said:
So I tried with SQLite indeed. But still, can you maybe try a support channel first? |
@driesvints I'm Sorry, I didn't think about SQLite. I'm sure that this is not intended behavior, it works perfectly outside the tests. The issue only occurs in tests. Checkout this fragment: It only commits when there is only one transaction, if I run the action directly without tests it runs because there is only one transaction but for testing, I need to use There is the line where
|
@mariomka can you maybe try a support channel first? If you can confirm the bug feel free to report back. |
@driesvints Thank you for your time but I don't know what you want me to ask the community. Seeing some comments on #35373 (#35373 (comment), #35373 (comment), #35373 (comment)) |
Hi, I've got the same issue. I've just discovered |
This feels wrong, then you do not know whether something was supposed to be done or not? |
Not entirely sure what you mean, but since |
Wait. Doesn't it support nested transactions? Apologies, because I use https://github.com/fntneves/laravel-transactional-events and the transaction opened via the trait (to wrap a test in transactions) does not count as influencing "transactionable ware" code triggered in nested transactions. (btw I use this together with Postgres with great success). It would be irritating to me to test a part of an application, which makes use of such a feature and then not knowing whether it was supposed to be executed or not just because the test is wrapped in a transaction. 🤔 |
@mariomka I have the same issue when using |
anyone find a solution for this? i am trying to test a job that creates some models in an DB transaction... and i am trying to test that some jobs are dispatched in the model observer... we are using afterCommit = true in the observer... and they are not being fired |
I just found this thread, and I didn't see an answer either. I went ahead and solved it myself. For everyone else coming here in the future, here's how I resolved this issue. The way Laravel handles the "after commit" functionality is by the new public function addCallback($callback)
{
if ($current = $this->transactions->last()) {
return $current->addCallback($callback);
}
call_user_func($callback);
} As seen above, if the connection currently has a transaction, all of the callbacks are bound to the ending of that transaction (which in the case of testing, has the scenario where it may never commit). I overrode the <?php
namespace App\Providers;
use App\Testing\DatabaseTransactionsManager;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
if (! $this->app->runningUnitTests()) {
return;
}
$this->registerDatabaseTransactionsManager();
}
/**
* Registers the database transaction manager.
*/
protected function registerDatabaseTransactionsManager()
{
// Our automated tests wrap everything inside of a database
// transactions, which means the "afterCommit" behavior
// is never invoked. We'll need to tweak this a bit.
$this->app->singleton('db.transactions', function () {
return new DatabaseTransactionsManager;
});
}
} This binds my own <?php
namespace App\Testing;
use Illuminate\Database\DatabaseTransactionsManager as Manager;
class DatabaseTransactionsManager extends Manager
{
/**
* The baseline transaction depth.
*/
protected $baselineDepth = 0;
/**
* Register a transaction callback.
*
* @param callable $callback
*
* @return void
*/
public function addCallback($callback)
{
if ($this->transactions->count() > $this->baselineDepth) {
return parent::addCallback($callback);
}
call_user_func($callback);
}
/**
* Updates the baseline transaction depth to the current transaction depth.
*
* @return $this
*/
public function captureTransactionDepth()
{
$this->baselineDepth = $this->transactions->count();
return $this;
}
} The new manager allows you to capture a "baseline depth", of which, upon reaching it, all callbacks are immediately fired, rather than deferred to a transaction committal. Now you just need to capture the transaction depth in the correct place, which can be done inside of your test: public function setUp(): void
{
parent::setUp();
$this->app->make('db.transactions')->captureTransactionDepth();
} And with that all in place, any "after commit" calls on top of the refresh transactions will be called immediately. If you have a transaction within your test, the callback will be deferred until the end of that specific transaction, but will still be called at some point in your test. |
@tylernathanreed your solution only work if only one transaction is perform in your application code. I implemented your solution, and with following code in my controller : DB::transaction(function () {
DB::afterCommit(function () { dump('This is executed'); });
});
DB::transaction(function () {});
DB::transaction(function () {});
DB::transaction(function () {
DB::afterCommit(function () { dd('This is not'); });
}); it doesn't dd() Because, each time you start a transaction with framework/src/Illuminate/Database/Concerns/ManagesTransactions.php Lines 48 to 52 in b9203fc
framework/src/Illuminate/Database/Concerns/ManagesTransactions.php Lines 194 to 198 in b9203fc
|
@driesvints Can you reconsider re-opening this issue ? It looks we are several people (including me) experiencing this issue with non-sqlite db driver. |
Sure. Appreciating PR's to solve this. |
@driesvints could you please also re-open this issue? |
+1 subscribing for the solution. For now this workaround works:
|
Potential fix #41782 Would appreciate feedback / testing. Of course, the other fix / existing work around is to use the |
Personally I very much appreciate everything @taylorotwell and his team are doing on managing a free framework that makes our life easier. |
I truly appreciate the kind words @shadoWalker89 but we're not going to change the way we work, sorry. |
Hey @driesvints I just ran into this |
I'm sorry @BrandonSurowiec but 8.x is closed for bug fixes. You should upgrade as soon as you can. |
Description:
DB::afterCommit
callbacks aren't run whenRefreshDatabase
trait is used becauseManagesTransactions
trait only commits when all transactions are executed, butRefreshDatabase
uses a transaction. Then the callbacks aren't called.Steps To Reproduce:
I've created a reproduction repo: https://github.com/mariomka/laravel-after-commit-repro
There are two tests, one with
RefreshDatabase
(it fails) and one withoutRefreshDatabase
(it passes): https://github.com/mariomka/laravel-after-commit-repro/tree/master/testsBoth tests run the same "action": https://github.com/mariomka/laravel-after-commit-repro/blob/master/app/AfterCommitAction.php
Step by step reproduction:
The text was updated successfully, but these errors were encountered: