Wrapping each test in a transaction and rolling back is much faster than migrating between tests, and three things do not survive it.
use RefreshDatabase; // transaction per test, rolled back
// what breaks:
// 1. code under test that commits, or uses DDL —
// MySQL commits implicitly on CREATE TABLE
// 2. a second connection: a browser test, a queue
// worker, anything not in this transaction
// 3. assertions about what a REAL commit triggers
// — after-commit callbacks never fire
The second connection case is the one that produces the most confusing failures: a browser driver hitting the application over HTTP opens its own connection and cannot see the uncommitted fixtures, so the page renders empty. That is what DatabaseTruncation or a migrated database is for, at a real speed cost. After-commit hooks silently not running is the subtle one, because the test passes and the behaviour is untested.