bentools/doctrine-static
Lightweight in-memory Doctrine mock for unit tests. Create ObjectManagers in a ManagerRegistry, persist/remove and flush to a temporary store, and use repository methods like find, findBy, findOneBy, and findAll. Not for real persistence.
## Technical Evaluation
### Architecture Fit
- **Test-Doubling Layer**: Perfectly aligns with **unit/integration testing** for Doctrine ORM, replacing database dependencies in CI/CD pipelines. Fits the **test pyramid** by enabling isolated business logic validation.
- **Laravel Context**: While Laravel primarily uses Eloquent, this package is valuable for:
- **Legacy Doctrine systems** integrated with Laravel.
- **Hybrid architectures** (e.g., API layers using Doctrine for complex queries).
- **Service-layer testing** where Doctrine repositories are injected.
- **Non-Production Scope**: Explicitly **not for production**—unsupported features (e.g., `UnitOfWork`, `merge`) make it unsuitable for persistence. Ideal for **test doubles** in **TDD** or **BDD** workflows.
### Integration Feasibility
- **Low Friction**: Can be **opt-in per test suite** via `phpunit.xml` or composer scripts, avoiding global impact.
- **Doctrine Dependency**: Requires `doctrine/annotations` and `doctrine/inflector` (peer dependencies), which may conflict with Laravel’s `doctrine/dbal` if not managed.
- **Entity Compatibility**:
- Works **natively** with `@ORM\Entity` classes.
- **Eloquent models** require wrapping in Doctrine entities or custom adapters.
- **Tooling Synergy**:
- Complements **Laravel’s `RefreshDatabase`** for hybrid testing (e.g., use Doctrine Static for unit tests, DB for integration).
- Integrates with **PHPUnit** via data providers or test traits.
### Technical Risk
- **Behavioral Gaps**:
- **False confidence**: Tests may pass with mocks but fail in production due to unsupported Doctrine features (e.g., lifecycle callbacks).
- **Edge cases**: Complex queries (e.g., `DQL`, `native SQL`) or cascading operations may not translate.
- **Maintenance Overhead**:
- **Custom test data** must be manually seeded (vs. database fixtures).
- **Test drift risk**: Mock data may diverge from production schema over time.
- **Performance**:
- **Memory usage**: In-memory arrays scale poorly for **large datasets** (mitigate by resetting state per test).
- **Cold starts**: Initialization overhead may slow down **parallel test suites**.
### Key Questions
1. **ORM Strategy**:
- Is Laravel using **pure Eloquent**, **Doctrine**, or a **hybrid**? If Eloquent, evaluate if this package adds value beyond native mocks.
- Are there **Doctrine-specific services** (e.g., custom repositories) that *must* be tested in isolation?
2. **Test Scope**:
- Will this replace **database transactions** (e.g., SQLite) or supplement them for **repository-level tests**?
- Are tests **unit-focused** (business logic) or **integration-focused** (API + DB)?
3. **Entity Complexity**:
- Are entities **simple DTOs** or **rich with Doctrine behaviors** (e.g., `@PrePersist`, inheritance mapping)?
- Do tests rely on **Doctrine events** (e.g., `postLoad`) or **metadata**?
4. **Tooling Ecosystem**:
- How will this interact with **Laravel’s testing helpers** (e.g., `DatabaseMigrations`, `MakesHttpRequests`)?
- Will it conflict with **existing mocking strategies** (e.g., `createMock`, `Mockery`)?
5. **Long-Term Viability**:
- With **0 dependents** and **low stars**, is the package **actively maintained**? Consider:
- Forking for Laravel-specific enhancements (e.g., Eloquent adapter).
- Building a **Laravel-first alternative** (e.g., `laravel-doctrine-mocker`).
- Does the **MIT license** align with your open-source policy?
---
## Integration Approach
### Stack Fit
- **Primary Use Case**:
- **Unit/integration tests** for Doctrine repositories/services in Laravel.
- **CI/CD acceleration**: Faster test execution by eliminating database setup.
- **Alternatives**:
- **Eloquent**: Use Laravel’s native mocks (`createMock`) or `DatabaseTransactions`.
- **Doctrine**: Leverage **Doctrine’s built-in interfaces** (`ObjectManager`, `Repository`) with `Mockery`.
- **Hybrid**: Use **Doctrine Static for repositories** + **Eloquent mocks for models** in mixed stacks.
- **When to Avoid**:
- **End-to-end tests**: Use **Pest/Dusk** or **Laravel HTTP tests** with a real DB.
- **Database-specific behaviors**: Transactions, triggers, or stored procedures.
### Migration Path
1. **Assessment Phase**:
- Audit **Doctrine-dependent tests** to identify candidates for migration.
- Prioritize **high-impact, slow tests** (e.g., repository-heavy suites).
2. **Pilot Implementation**:
- **Step 1**: Replace **1–2 critical test files** with Doctrine Static.
Example:
```php
// Before: DatabaseTransaction
public function testFindUserByEmail() {
$user = User::factory()->create(['email' => '[email protected]']);
$this->assertEquals($user, User::where('email', '[email protected]')->first());
}
// After: DoctrineStatic
public function testFindUserByEmail() {
$registry = new ManagerRegistry([
'default' => new ObjectManager([
new ObjectRepository(User::class, [
new User(1, '[email protected]')
]),
]),
]);
$user = $registry->getManager()->find(User::class, 1);
$this->assertEquals('[email protected]', $user->getEmail());
}
```
- **Step 2**: Add a **custom test trait** for reuse:
```php
trait UsesDoctrineStatic {
protected ManagerRegistry $doctrine;
protected function createDoctrineStatic(): void {
$this->doctrine = new ManagerRegistry([
'default' => new ObjectManager([
new ObjectRepository(User::class, []),
new ObjectRepository(Post::class, []),
]),
]);
}
protected function persistAndFlush(object $entity): void {
$this->doctrine->getManager()->persist($entity);
$this->doctrine->getManager()->flush();
}
}
```
3. **Incremental Rollout**:
- **Phase 1**: Mock **simple CRUD** (`persist`, `find`, `findBy`).
- **Phase 2**: Extend to **complex queries** (e.g., `findBy` with multiple conditions, joins).
- **Phase 3**: Replace **database fixtures** with pre-populated mock arrays.
- **Phase 4**: Integrate with **data factories** (e.g., `Faker`) for dynamic test data.
### Compatibility
- **Doctrine Entities**: **Zero changes** required—works with `@ORM\Entity` classes.
- **Laravel Eloquent**:
- **Option 1**: Wrap Eloquent models in Doctrine entities for tests.
- **Option 2**: Use **adapters** to bridge Eloquent and Doctrine Static (e.g., custom `ObjectRepository`).
- **Dependencies**:
- **Required**: `doctrine/annotations`, `doctrine/inflector` (may conflict with Laravel’s `doctrine/dbal`).
- **Mitigation**: Use **composer’s `replace`** or **aliases** to avoid conflicts.
- **Optional**: `doctrine/orm` (only for type hints in tests; not runtime dependencies).
- **Laravel Services**:
- **Doctrine-based services**: Directly replace `EntityManager` with `ManagerRegistry`.
- **Eloquent-based services**: Mock dependencies at the **repository layer**.
### Sequencing
1. **Pre-requisites**:
- Ensure **Doctrine Common** is installed (may require composer adjustments).
- Add to `composer.json` under `require-dev`:
```json
"dev-dependencies": {
"benools/doctrine-static": "1.0.x-dev",
"doctrine/annotations": "^2.0",
"doctrine/inflector": "^2.0"
}
```
2. **Test Isolation**:
- Use **separate `phpunit.xml`** for Doctrine Static tests to avoid polluting the main suite.
Example:
```xml
<testsuites>
<testsuite name="DoctrineStatic Tests">
<directory>./tests/DoctrineStatic</directory>
</testsuite>
<testsuite name="Database Tests">
<directory>./tests/Database</directory>
</testsuite>
</testsuites>
```
3. **CI/CD Integration**:
- Add a **dedicated test stage** for Doctrine Static tests:
```yaml
# .github/workflows/tests.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: composer install
- run: vendor/bin/phpunit --testsuite="DoctrineStatic"
```
4. **Hybrid Testing**:
- Use **feature flags** to toggle between mock and real DB in tests:
```php
if (config('testing.use_doctrine_static')) {
How can I help you explore Laravel packages today?