Skip to content

Commit

Permalink
Reload objects (#11)
Browse files Browse the repository at this point in the history
* Reload objects

* Added some more tests

* Fixed tests

* cs

* Fixes

* cs
  • Loading branch information
Nyholm authored Dec 26, 2020
1 parent 69b1f33 commit 3471573
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 38 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.

## 0.1.3

### Changed

- The real object is updated whenever a proxy is initialized.

## 0.1.2

### Changed
Expand Down
14 changes: 9 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
"name": "happyr/service-mocking",
"type": "symfony-bundle",
"description": "Make it easy to mock services in a built container",
"keywords": ["Symfony", "testing", "mock"],
"keywords": [
"Symfony",
"testing",
"mock"
],
"license": "MIT",
"authors": [
{
Expand All @@ -17,6 +21,10 @@
"symfony/dependency-injection": "^4.4 || ^5.1",
"symfony/http-kernel": "^4.4 || ^5.1"
},
"require-dev": {
"nyholm/symfony-bundle-test": "^1.6",
"symfony/phpunit-bridge": "^4.4 || ^5.2"
},
"autoload": {
"psr-4": {
"Happyr\\ServiceMocking\\": "src/"
Expand All @@ -26,9 +34,5 @@
"psr-4": {
"Happyr\\ServiceMocking\\Tests\\": "tests/"
}
},
"require-dev": {
"nyholm/symfony-bundle-test": "^1.6",
"symfony/phpunit-bridge": "^4.4 || ^5.2"
}
}
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ parameters:
count: 1
path: src/Generator/LazyLoadingValueHolderGenerator.php

-
message: "#^Call to an undefined method ProxyManager\\\\Proxy\\\\LazyLoadingInterface\\:\\:getWrappedValueHolderValue\\(\\)\\.$#"
count: 1
path: src/ServiceMock.php

8 changes: 8 additions & 0 deletions psalm.baseline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.3.1@2feba22a005a18bf31d4c7b9bdb9252c73897476">
<file src="src/ServiceMock.php">
<UndefinedInterfaceMethod occurrences="1">
<code>getWrappedValueHolderValue</code>
</UndefinedInterfaceMethod>
</file>
</files>
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm.baseline.xml"
>
<projectFiles>
<directory name="src" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function process(ContainerBuilder $container)
\spl_autoload_register($config->getProxyAutoloader());
$factory = new GeneratorFactory($config);

foreach ($serviceIds as $serviceId) {
foreach (array_unique($serviceIds) as $serviceId) {
if ($container->hasDefinition($serviceId)) {
$definition = $container->getDefinition($serviceId);
} elseif ($container->hasAlias($serviceId)) {
Expand Down
20 changes: 14 additions & 6 deletions src/Proxy/ProxyDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ public function __construct(object $originalObject)
$this->originalObject = $originalObject;
}

public function swap(object $replacement): void
{
$this->clear();
$this->replacement = $replacement;
}

public function clear(): void
{
$this->methods = [];
$this->methodsQueue = [];
$this->replacement = null;
}

public function swap(object $replacement): void
{
$this->clear();
$this->replacement = $replacement;
}

/**
* Get an object to execute a method on.
*/
Expand All @@ -47,6 +47,14 @@ public function getOriginalObject(): object
return $this->originalObject;
}

/**
* @internal
*/
public function setOriginalObject($originalObject): void
{
$this->originalObject = $originalObject;
}

public function getMethodCallable(string $method): ?callable
{
if (isset($this->methodsQueue[$method])) {
Expand Down
19 changes: 16 additions & 3 deletions src/ServiceMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static function swap($proxy, object $replacement): void
$definition->swap($replacement);

// Initialize now so we can use it directly.
self::initializeProxy($proxy);
self::addInitializer($proxy);
}

/**
Expand All @@ -35,7 +35,7 @@ public static function next($proxy, string $methodName, callable ...$func): void
}

// Initialize now so we can use it directly.
self::initializeProxy($proxy);
self::addInitializer($proxy);
}

/**
Expand All @@ -47,7 +47,7 @@ public static function all($proxy, string $methodName, callable $func): void
$definition->addMethod($methodName, $func);

// Initialize now so we can use it directly.
self::initializeProxy($proxy);
self::addInitializer($proxy);
}

/**
Expand Down Expand Up @@ -79,7 +79,20 @@ public static function resetMethod($proxy, string $methodName): void
$definition->clearMethodsQueue($methodName);
}

/**
* This method is called in the proxy's constructor.
*
* @internal
*/
public static function initializeProxy(LazyLoadingInterface $proxy): void
{
$definition = self::getDefinition($proxy);
// Make sure the definition always have the latest original object.
$definition->setOriginalObject($proxy->getWrappedValueHolderValue());
self::addInitializer($proxy);
}

private static function addInitializer(LazyLoadingInterface $proxy): void
{
$initializer = function (&$wrappedObject, LazyLoadingInterface $proxy, $calledMethod, array $parameters, &$nextInitializer) {
$nextInitializer = null;
Expand Down
6 changes: 4 additions & 2 deletions src/Test/RestoreServiceContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Happyr\ServiceMocking\Test;

use Happyr\ServiceMocking\ServiceMock;

/**
* After each tests, make sure we restore the default behavior of all
* After each test, make sure we restore the default behavior of all
* services.
*
* @author Tobias Nyholm <[email protected]>
Expand All @@ -12,7 +14,7 @@ trait RestoreServiceContainer
{
/**
* @internal
* @before
* @after
*/
public static function _restoreContainer(): void
{
Expand Down
79 changes: 59 additions & 20 deletions tests/Functional/BundleInitializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,13 @@

use Happyr\ServiceMocking\HappyrServiceMockingBundle;
use Happyr\ServiceMocking\ServiceMock;
use Happyr\ServiceMocking\Tests\Resource\ExampleService;
use Happyr\ServiceMocking\Tests\Resource\StatefulService;
use Nyholm\BundleTest\BaseBundleTestCase;
use Nyholm\BundleTest\CompilerPass\PublicServicePass;
use ProxyManager\Proxy\VirtualProxyInterface;
use Symfony\Bundle\FrameworkBundle\Routing\Router;

class BundleInitializationTest extends BaseBundleTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->addCompilerPass(new PublicServicePass('|router|'));
}

protected function getBundleClass()
{
return HappyrServiceMockingBundle::class;
Expand All @@ -35,29 +29,74 @@ public function testInitBundle()
// Get the container
$container = $this->getContainer();

$this->assertTrue($container->has('router'));
$service = $container->get('router');
$this->assertTrue($container->has(ExampleService::class));
$service = $container->get(ExampleService::class);

$this->assertInstanceOf(Router::class, $service);
$this->assertInstanceOf(ExampleService::class, $service);
$this->assertInstanceOf(VirtualProxyInterface::class, $service);

$called = false;
ServiceMock::next($service, 'warmUp', function ($dir) use (&$called) {
ServiceMock::next($service, 'getNumber', function ($dir) use (&$called) {
$called = true;
$this->assertSame('foo', $dir);
$this->assertSame(11, $dir);

return 17;
});

$service->warmUp('foo');
$this->assertSame(17, $service->getNumber(11));
$this->assertTrue($called);

$mock = $this->getMockBuilder(\stdClass::class)
->disableOriginalConstructor()
->addMethods(['warmUp'])
->addMethods(['getNumber'])
->getMock();
$mock->expects($this->once())->method('warmUp')->willReturn(true);
$mock->expects($this->once())->method('getNumber')->willReturn(2);
ServiceMock::swap($service, $mock);

$this->assertTrue($service->warmUp('foo'));
$this->assertSame(2, $service->getNumber());
}

public function testRebootBundle()
{
$kernel = $this->createKernel();
$kernel->addConfigFile(__DIR__.'/config.yml');

$this->bootKernel();
$container = $this->getContainer();

$this->assertTrue($container->has(StatefulService::class));
$service = $container->get(StatefulService::class);
$service->setData('foobar');
$this->assertNotNull($service->getData());
ServiceMock::next($service, 'getData', function () {
return 'secret';
});

$this->bootKernel();

$container = $this->getContainer();
$service = $container->get(StatefulService::class);
$this->assertSame('secret', $service->getData());
$this->assertNull($service->getData());
}

public function testReloadRealObjectOnRebootBundle()
{
$kernel = $this->createKernel();
$kernel->addConfigFile(__DIR__.'/config.yml');

$this->bootKernel();
$container = $this->getContainer();

$this->assertTrue($container->has(StatefulService::class));
$service = $container->get(StatefulService::class);
$service->setData('foobar');
$this->assertNotNull($service->getData());
$this->bootKernel();

$container = $this->getContainer();
$service = $container->get(StatefulService::class);
$this->assertNull($service->getData(), 'The real service object is not reloaded on kernel reboot.');
}

public function testInitEmptyBundle()
Expand All @@ -71,9 +110,9 @@ public function testInitEmptyBundle()
// Get the container
$container = $this->getContainer();

$this->assertTrue($container->has('router'));
$service = $container->get('router');
$this->assertTrue($container->has(ExampleService::class));
$service = $container->get(ExampleService::class);

$this->assertInstanceOf(Router::class, $service);
$this->assertInstanceOf(ExampleService::class, $service);
}
}
11 changes: 10 additions & 1 deletion tests/Functional/config.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
happyr_service_mocking:
services:
- 'router'
- 'Happyr\ServiceMocking\Tests\Resource\ExampleService'

services:

Happyr\ServiceMocking\Tests\Resource\ExampleService:
public: true

Happyr\ServiceMocking\Tests\Resource\StatefulService:
tags:
- { name: happyr_service_mock }
4 changes: 4 additions & 0 deletions tests/Functional/empty.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:

Happyr\ServiceMocking\Tests\Resource\ExampleService:
public: true
13 changes: 13 additions & 0 deletions tests/Resource/ExampleService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Happyr\ServiceMocking\Tests\Resource;

class ExampleService
{
public function getNumber(int $input = 0): int
{
return 4711 + $input;
}
}
20 changes: 20 additions & 0 deletions tests/Resource/StatefulService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Happyr\ServiceMocking\Tests\Resource;

class StatefulService
{
private $data;

public function getData()
{
return $this->data;
}

public function setData($data): void
{
$this->data = $data;
}
}
23 changes: 23 additions & 0 deletions tests/Unit/Proxy/ProxyDefinitionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Happyr\ServiceMocking\Tests\Unit\Proxy;

use Happyr\ServiceMocking\Proxy\ProxyDefinition;
use PHPUnit\Framework\TestCase;

class ProxyDefinitionTest extends TestCase
{
public function testSwap()
{
$a = new \stdClass();
$proxy = new ProxyDefinition($a);
$this->assertSame($a, $proxy->getObject());

$b = new \stdClass();
$proxy->swap($b);
$this->assertSame($b, $proxy->getObject());

$proxy->clear();
$this->assertSame($a, $proxy->getObject());
}
}

0 comments on commit 3471573

Please sign in to comment.