Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable module override when test. #12

Merged
merged 14 commits into from
Mar 28, 2023
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