Skip to content

Commit

Permalink
Merge pull request #12 from NaokiTsuchiya/feat/override-module
Browse files Browse the repository at this point in the history
Enable module override when test.
  • Loading branch information
koriym authored Mar 28, 2023
2 parents 2d97eec + cb2fb4a commit fa40da9
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 4 deletions.
24 changes: 24 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,30 @@ apcu拡張が有効な場合インジェクターをキャッシュします。
独自のコンテキストが必要になる場合もあります。
組み込みコンテキストを参考にカスタムコンテキストを実装し、`RayDi/Context/ContextProvider`で利用するようにします。

### モジュールのオーバーライド

テストケースによって束縛を変更したい場合があります。
その場合は、`Ray\RayDiForLaravel\Testing\OverrideModule`を利用し、テストクラスで`$this->overrideModule` を呼び出してください。

```php
use Tests\TestCase;

final class HelloTest extends TestCase
{
use Ray\RayDiForLaravel\Testing\OverrideModule;

public function testStatusOk(): void
{
$this->overrideModule(new MyModule());

$res = $this->get('/hello');

$res->assertOk();
$res->assertSeeText('Hello 1 * 2 = 2.');
}
}
```

## パフォーマンス

[DiCompileModule](https://github.com/ray-di/Ray.Compiler/blob/1.x/src/DiCompileModule.php)をインストールすることで、
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ In the `RayDi/Context/ProductionContext`, the injector is cached if the apcu ext
You may need your own context.
Implement a custom context with reference to the built-in context and use it in `RayDi/Context/ContextProvider`.

### Overriding Modules

When running tests, you may want to change the binding depending on the test case.

Use `Ray\RayDiForLaravel\Testing\OverrideModule` in your test class and call `$this->overrideModule` as shown below.

```php
use Tests\TestCase;

final class HelloTest extends TestCase
{
use Ray\RayDiForLaravel\Testing\OverrideModule;

public function testStatusOk(): void
{
$this->overrideModule(new MyModule());

$res = $this->get('/hello');

$res->assertOk();
$res->assertSeeText('Hello 1 * 2 = 2.');
}
}
```

## Performance

By installin the [DiCompileModule](https://github.com/ray-di/Ray.Compiler/blob/1.x/src/DiCompileModule.php), An optimized injector is used and dependency errors are reported at compile time, not at runtime.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"require": {
"php": ">=8.0",
"ray/di": "^2.15.1",
"ray/compiler": "^1.9.2",
"ray/compiler": "^1.10",
"doctrine/cache": "^1.10 || ^2.2"
},
"require-dev": {
Expand Down
43 changes: 42 additions & 1 deletion src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Contracts\Container\BindingResolutionException;
use Ray\Compiler\AbstractInjectorContext;
use Ray\Compiler\ContextInjector;
use Ray\Di\AbstractModule;
use Ray\Di\Exception\Unbound;
use Ray\Di\InjectorInterface;
use Ray\RayDiForLaravel\Attribute\Injectable;
Expand All @@ -22,10 +23,17 @@ class Application extends \Illuminate\Foundation\Application
/** @var string[] */
private array $abstractsResolvedByRay = [];

private AbstractInjectorContext $context;

private AbstractModule|null $overrideModule = null;

private InjectorInterface|null $overrideInjectorInstance = null;

public function __construct(string $basePath, AbstractInjectorContext $injectorContext)
{
parent::__construct($basePath);
$this->injector = ContextInjector::getInstance($injectorContext);
$this->context = $injectorContext;
}

protected function resolve($abstract, $parameters = [], $raiseEvents = true)
Expand All @@ -34,8 +42,10 @@ protected function resolve($abstract, $parameters = [], $raiseEvents = true)
return parent::resolve($abstract, $parameters, $raiseEvents);
}

$injector = $this->getInjector();

try {
return $this->injector->getInstance($abstract);
return $injector->getInstance($abstract);
} catch (Unbound $e) {
throw new BindingResolutionException("Failed to resolve {$abstract} by Ray's injector.", 0, $e);
}
Expand All @@ -61,4 +71,35 @@ private function shouldBeResolvedByRay(string $abstract): bool
$this->abstractsResolvedByRay[] = $abstract;
return true;
}

public function flush()
{
parent::flush();

$this->overrideModule = null;
$this->abstractsResolvedByRay = [];
$this->overrideInjectorInstance = null;
}

public function overrideModule(AbstractModule $module): void
{
$this->overrideModule = $module;
$this->overrideInjectorInstance = null;
}

private function getInjector(): InjectorInterface
{
if ($this->overrideModule === null) {
return $this->injector;
}

if ($this->overrideInjectorInstance !== null) {
return $this->overrideInjectorInstance;
}

$injector = ContextInjector::getOverrideInstance($this->context, $this->overrideModule);
$this->overrideInjectorInstance = $injector;

return $injector;
}
}
24 changes: 24 additions & 0 deletions src/Testing/OverrideModule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Ray\RayDiForLaravel\Testing;

use Ray\Di\AbstractModule;
use Ray\RayDiForLaravel\Application;

trait OverrideModule
{
protected function overrideModule(AbstractModule $module): void
{
if (! property_exists($this, 'app')) {
return; // @codeCoverageIgnore
}

if (! $this->app instanceof Application) {
return; // @codeCoverageIgnore
}

$this->app->overrideModule($module);
}
}
69 changes: 69 additions & 0 deletions tests/ApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
use Illuminate\Contracts\Container\BindingResolutionException;
use PHPUnit\Framework\TestCase;
use Ray\Compiler\AbstractInjectorContext;
use Ray\Di\AbstractModule;
use Ray\RayDiForLaravel\Classes\FakeCacheableContext;
use Ray\RayDiForLaravel\Classes\FakeContext;
use Ray\RayDiForLaravel\Classes\FakeGreeting;
use Ray\RayDiForLaravel\Classes\FakeInvalidContext;
use Ray\RayDiForLaravel\Classes\GreetingInterface;
use Ray\RayDiForLaravel\Classes\GreetingServiceProvider;
use Ray\RayDiForLaravel\Classes\IlluminateGreeting;
use Ray\RayDiForLaravel\Classes\InjectableService;
use Ray\RayDiForLaravel\Classes\NonInjectableService;
use Ray\RayDiForLaravel\Classes\OtherModule;
use Ray\RayDiForLaravel\Classes\OverrideGreetingModule;

class ApplicationTest extends TestCase
{
Expand Down Expand Up @@ -91,6 +96,70 @@ public function testFailedToResolveByRayDi(): void
$app->make(InjectableService::class);
}

/** @depends testResolvedByRayWhenMarkedClassGiven */
public function testOverrideModule(Application $application): Application
{
$overrideModule = new OverrideGreetingModule();
$application->overrideModule($overrideModule);

$instance = $application->make(InjectableService::class);

$this->assertInstanceOf(InjectableService::class, $instance);
$this->assertSame('Hello, override!', $instance->run());

return $application;
}

/** @depends testOverrideModule */
public function testAlreadyResolvedOverrideModule(Application $application): void
{
$instance = $application->make(InjectableService::class);

$this->assertInstanceOf(InjectableService::class, $instance);
$this->assertSame('Hello, override!', $instance->run());
}

/** @depends testOverrideModule */
public function testSameModuleAndHasParentModule(Application $application): void
{
$application->overrideModule(new OverrideGreetingModule(new OtherModule()));

$instance = $application->make(InjectableService::class);

$this->assertInstanceOf(InjectableService::class, $instance);
$this->assertSame('Hello, override!Fake', $instance->run());
}

/** @depends testOverrideModule */
public function testSetOtherOverrideModule(Application $application): void
{
$overrideModule = new class extends AbstractModule {

protected function configure()
{
$this->bind(GreetingInterface::class)->to(FakeGreeting::class);
}
};

$application->overrideModule($overrideModule);

$instance = $application->make(InjectableService::class);

$this->assertInstanceOf(InjectableService::class, $instance);
$this->assertSame('Hello, fake!', $instance->run());
}

/** @depends testOverrideModule */
public function testFlush(Application $application): void
{
$application->flush();

$instance = $application->make(InjectableService::class);

$this->assertInstanceOf(InjectableService::class, $instance);
$this->assertSame('Hello, Ray! Intercepted.', $instance->run());
}

/**
* @param class-string<AbstractInjectorContext> $contextClass
*/
Expand Down
13 changes: 13 additions & 0 deletions tests/Classes/FakeFoo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ray\RayDiForLaravel\Classes;

class FakeFoo implements FooInterface
{
public function __invoke(): string
{
return 'Fake';
}
}
13 changes: 13 additions & 0 deletions tests/Classes/FakeGreeting.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ray\RayDiForLaravel\Classes;

class FakeGreeting implements GreetingInterface
{
public function greet(): string
{
return 'Hello, fake!';
}
}
13 changes: 13 additions & 0 deletions tests/Classes/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ray\RayDiForLaravel\Classes;

class Foo implements FooInterface
{
public function __invoke(): string
{
return '';
}
}
8 changes: 8 additions & 0 deletions tests/Classes/FooInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Ray\RayDiForLaravel\Classes;

interface FooInterface
{
public function __invoke(): string;
}
5 changes: 3 additions & 2 deletions tests/Classes/InjectableService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
#[Injectable]
final class InjectableService
{
public function __construct(private GreetingInterface $greeting, private FakeInterface $fake)
public function __construct(private GreetingInterface $greeting, private FakeInterface $fake, private FooInterface $foo)
{
}

public function run(): string
{
($this->fake)();
$value = ($this->foo)();

return $this->greeting->greet();
return $this->greeting->greet() . $value;
}
}
1 change: 1 addition & 0 deletions tests/Classes/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protected function configure(): void
{
$this->bind(GreetingInterface::class)->to(RayGreeting::class)->in(Scope::SINGLETON);
$this->bind(FakeInterface::class)->to(Fake::class);
$this->bind(FooInterface::class)->to(Foo::class);
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith(StringDecorator::class),
Expand Down
15 changes: 15 additions & 0 deletions tests/Classes/OtherModule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Ray\RayDiForLaravel\Classes;

use Ray\Di\AbstractModule;

class OtherModule extends AbstractModule
{
protected function configure()
{
$this->bind(FooInterface::class)->to(FakeFoo::class);
}
}
13 changes: 13 additions & 0 deletions tests/Classes/OverrideGreeting.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ray\RayDiForLaravel\Classes;

class OverrideGreeting implements GreetingInterface
{
public function greet(): string
{
return 'Hello, override!';
}
}
16 changes: 16 additions & 0 deletions tests/Classes/OverrideGreetingModule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Ray\RayDiForLaravel\Classes;

use Ray\Di\AbstractModule;

class OverrideGreetingModule extends AbstractModule
{

protected function configure()
{
$this->bind(GreetingInterface::class)->to(OverrideGreeting::class);
}
}
Loading

0 comments on commit fa40da9

Please sign in to comment.