Master Testing DDD components in Laravel. Learn to mock external services, isolate domain logic, and write reliable PHPUnit tests for your Action classes.
Previously in this course, we explored distributed tracing to visualize complex execution paths in our SaaS platform. While tracing helps us observe production behavior, we need a proactive strategy to ensure our domain logic remains correct as we scale. This lesson focuses on Testing DDD components, specifically how to isolate business rules from infrastructure, allowing us to evolve our architecture without breaking critical features.
In a Domain-Driven Design (DDD) approach, your core business logic—residing in Action classes and Service layers—should be agnostic of the database, the framework, or external APIs.
If your test suite hits a live Stripe API or requires a complex database state to verify a simple calculation, you aren't testing logic; you're testing the environment. To test effectively, we must strictly separate Unit Tests (logic-focused) from Integration Tests (infrastructure-focused).
Action classes are the entry points for your domain logic. To test them without triggering side effects, we rely on dependency injection and testing with test doubles.
Consider a ProcessSubscriptionUpgrade action that depends on a PaymentGatewayInterface.
PHPnamespace Domain\Billing\Actions; use Domain\Billing\Contracts\PaymentGatewayInterface; use Domain\Billing\DTOs\UpgradeData; class ProcessSubscriptionUpgrade { public function __construct( private PaymentGatewayInterface $gateway ) {} public function execute(UpgradeData $data): bool { #6A9955">// Domain logic: validation, calculation if ($data->amount <= 0) return false; #6A9955">// Infrastructure interaction via interface return $this->gateway->charge($data->user, $data->amount); } }
To test this, we don't need a real gateway. We mock the interface.
PHPpublic function test_it_successfully_upgrades_subscription() { $gateway = $this->createMock(PaymentGatewayInterface::class); $gateway->expects($this->once()) ->method('charge') ->willReturn(true); $action = new ProcessSubscriptionUpgrade($gateway); $result = $action->execute(new UpgradeData(user: $user, amount: 100)); $this->assertTrue($result); }
When your domain components depend on external services (like an email provider or a CRM), mocking is non-negotiable. Using Laravel's Mockery integration makes this expressive and type-safe.
| Strategy | Best For | Trade-off |
|---|---|---|
| Fakes | Laravel-native services (Mail, Queue, Event) | Less granular control |
| Mocks | Third-party API interfaces | Requires strict contract adherence |
| Stubs | Data-heavy dependencies | Doesn't verify interaction behavior |
If you are calling a third-party service, wrap it in a custom interface. This allows you to use Mockery to verify that your domain layer is passing the correct data structures.
In our ongoing SaaS project, navigate to app/Domain/Billing. Identify your primary CreateInvoice action.
tests/Unit/Domain/Billing/CreateInvoiceTest.php file.TaxCalculator service?TaxCalculator to return a fixed value, ensuring your CreateInvoice logic correctly adds this tax to the final total.Testing DDD components is about creating a clear boundary between "what" your business does and "how" the system executes it. By mocking interfaces and injecting dependencies, you ensure your domain logic is portable, readable, and—most importantly—verifiable without needing a full environment spin-up.
Up next, we will explore Contract Testing to ensure that when our domain services communicate, they stay in sync with their expected interfaces.
Master contract testing in Laravel to ensure your decoupled modules stay compatible. Learn to implement consumer-driven contracts to prevent breaking changes.
Read moreLearn to build a Modular Monolith by structuring your Laravel directory by domain, enforcing encapsulation, and defining public interfaces for module communication.
Testing DDD Components
Custom Middleware Development
Database Connection Pooling
Handling Large Data Exports
Security Header Configuration
Database Sharding Concepts
Real-time Data Synchronization
Database Deadlock Prevention
Managing Third-Party API Integrations