Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Integration Tests vs. Unit Tests: When Each One Makes Sense

Integration Tests vs. Unit Tests: When Each One Makes Sense

~6 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Chapter 92 ended on an open question: the happy path of AwardPointsOnOrderPlaced - a real order, a real product, a real persisted ledger row - can theoretically be mocked in full, but the effort is out of proportion to what it would actually prove. That's exactly the boundary where integration tests take over.

Two different questions

A unit test answers: "Is this one piece of decision logic correct, in isolation from everything around it?" An integration test answers a different question: "Do the real Magento building blocks actually work together the way the configuration promises?" - does db_schema.xml really load the right table, does sales_order_place_after really fire the registered observer, does the EAV resource model really persist across all five attribute tables? No mock can answer these questions, because a mock by definition never runs through Magento's real wiring.

Where Magento's integration tests run

dev/tests/integration/ bootstraps a real, separate Magento installation against its own test database (configured in dev/tests/integration/etc/install-config-mysql.php.dist), with a real object manager, real plugins, real observers, real db_schema.xml tables. Every test case runs in a transaction by default, rolled back at the end - the test database stays clean between individual tests with no manual cleanup.

// Excerpt, illustrative: an integration test for the actual database persistence
// from chapter 6, instead of fully mocking the PointsLedgerRepositoryInterface
// it uses there.
namespace Mironsoft\Loyalty\Test\Integration\Model;

use Magento\TestFramework\Helper\Bootstrap;
use Mironsoft\Loyalty\Api\Data\PointsLedgerInterfaceFactory;
use Mironsoft\Loyalty\Api\PointsLedgerRepositoryInterface;
use PHPUnit\Framework\TestCase;

/**
 * @magentoDbIsolation enabled
 */
class PointsLedgerRepositoryTest extends TestCase
{
    public function testSaveAndGetByIdRoundTrip(): void
    {
        $objectManager = Bootstrap::getObjectManager();
        $repository = $objectManager->create(PointsLedgerRepositoryInterface::class);
        $ledgerFactory = $objectManager->create(PointsLedgerInterfaceFactory::class);

        $entry = $ledgerFactory->create();
        $entry->setCustomerId(1)->setPoints(100)->setType('earn')->setBalanceAfter(100);

        $saved = $repository->save($entry);
        $loaded = $repository->getById((int) $saved->getLedgerId());

        self::assertSame(100, $loaded->getPoints());
    }
}

Tipp: @magentoDbIsolation enabled is exactly the annotation that turns on automatic transaction rollback after every test - without it, test ledger entries pile up in the test database.

A rule of thumb for this module

  • Unit test: pure decision logic with no framework involvement - PointsCalculator (chapter 91), an observer's guard clauses (chapter 92), the reconciliation arithmetic in ExpirePoints (chapter 33).
  • Integration test: anything whose correctness depends on real Magento wiring - PointsLedgerRepository against the real table (chapter 6), LoyaltyTierBackend::beforeSave() as a registered EAV backend model (chapter 26), whether sales_order_place_after actually triggers AwardPointsOnOrderPlaced (chapter 30), a reward's full EAV persistence across all five attribute tables (chapters 11-12).
  • Both together: verify the decision logic in an isolated unit test, verify the wiring in a separate, leaner integration test - never rebuild the same fact in both test types redundantly.

Achtung: Integration tests are orders of magnitude slower than unit tests - fully bootstrapping the test installation costs noticeable time before the actual test code even runs. Writing PointsCalculator (chapter 91) as an integration test instead of a unit test measurably slows the test suite down without covering a single additional failure case - the class simply has no framework dependency an integration test could verify.

Chapter 94 builds on this distinction and answers the next question: how much of all this actually needs to be tested.