I'm wondering if there is a sort of best practice for the following case.
For instance, I have several services and inject them all as an array into a "factory" service. Then I call a method of this factory and want to get only one service depends on some conditions. After that I execute this service and get a result...
However, some of these services require a random string that I get from a client's request.
Of cause, I can call a service's method with this string as a parameter but several services do not require this string and I'll get "unused variable" in the method.
I guess that I could get service from the factory and then call a setter to add this string into the service. But it does not look like a stateless service.
Is there a more elegant solution to pass parameters that I could not inject into service nor use the setter for it?
Here how it looks in my code
First, I have an interface of all servers that I want to check. The service should support a customer an then it should render information from a DTO.
interface Renderable {
public function supports(Customer $customer);
public function render(CustomerDTO $dto);
}
Next, I have several services. This one uses DTO to render data.
class ServiceOne implements Renderable
{
public function suppots(Customer $customer)
{
return $customer->getPriority() === 1;
}
public function render(CustomerDTO $dto)
{
return 'One: '.$dto->getName();
}
}
However, some services do not need any DTO to render, they just provide a hardcoded value.
class ServiceTwo implements Renderable
{
public function suppots(Customer $customer)
{
return $customer->getPriority() !== 1;
}
// service does not use DTO, it simply output result
// so, I'll get a notice about unused variable
// and I can not remove it from the method since it is in interface
public function render(CustomerDTO $dto)
{
return 'Two';
}
}
This is a factory. It has all services injected as an array. Then it checks and returns the first service that supports a customer instance.
class ServiceFactory
{
/** #var Renderable[] */
private $services;
public function __construct(iterable $services)
{
$this->services = $services;
}
public function getRenderer(Customer $customer)
{
foreach ($this->services as $service)
{
if ($service->supports($customer)
{
return $service;
}
}
}
}
Here like I use factory and its result
$customer = ...; // it comes from a database
$request = ...; // it comes from a http request
$renderService = $factory->getRenderer($customer);
$customerDTO = CustomerDTO::createFromData([
'customerUid' => $customer->getUid(),
'date' => new \DateTime(),
'name' => $request->getSheetUid(),
'tags' => $request->getTags(),
]);
$renderService->render($customerDTO);
So, I have to call Renderer::render with a DTO instance. But some services do not use it to "render" data. I also can not inject it into a renderer service since this object (DTO) is built in a runtime when all services already injected. I also can not inject a RequestStack into service.
Since your parameter came from request - it can't be directly injected into service. Depending on actual logic of your services you can consider one of approaches listed below. Let's call your "random string that came from a client's request" a $requestParam for further reference.
In both cases you will need to get your $requestParam from actual Request object and pass it somewhere else. It can be done in a different ways, I would propose to create listener (e.g. RequestParamListener) for kernel.request event and put here a piece of code that takes parameter from Request and pass it further into this listener. Into approaches listed below I will assume that $requestParam will be passed in this way.
1. Separate provider
You can create separate class (e.g. RequestParamProvider) that will act as provider of this $requestParam for other services. It will receive $requestParam from RequestParamListener and other services that needs to get $requestParam will need to inject this provider and use its getRequestParam() method to obtain required parameter.
From my point of view it is the simplest approach and I would recommend it.
2. Direct injection by factory
Since you have some factory service - you can pass this $requestParam directly into factory and let it to initialize other services. Less flexible because you will need to implement initialization logic by itself and maintain it while project evolves.
3. Direct injection using interface
You can create separate interface (e.g. RequestParamAwareInterface) that will contain setRequestParam() method and let all classes that needs this $requestParam to implement this interface. After that you will need to write separate compiler pass that will collect all such classes (by iterating over ContainerBuilder and looking for implementation of particular interface by class inside service's definition) and pass array of these services to your RequestParamListener. Listener in its turn will be obligated to pass $requestParam for each of given services.
This approach will let your application to grow without need to sync $requestParam injection logic. However it will came at a cost of preliminary instantiation of all affected services regardless of actual further use of created instances.
I have a class that requires the Symfony2 service #request_stack which returns an instance of Symfony\Component\HttpFoundation\RequestStack. I use it to retrieve POST and GET values.
And also my class uses Symfony\Component\HttpFoundation\Session from Request->getSession() which it calls to get the current session.
Right now my class has a method that looks something like this:
class MyClass {
public function doSomething() {
//Get request from request stack.
$Request = $this->RequestStack->getCurrentRequest();
//Get a variable from request
$var = $Request->request->get('something');
//Processes $var into $someprocessedvar and lets say it's equal to 3.
//Set value to session.
$this->Request->getSession()->set('somevar', $someprocessedvar);
}
}
I need to be able to:
Mock RequestStack.
Get Request from RequestStack
Get Session from Request;
With all that said how can I test that MyClass successfully set the expected value in the session?
Not all code is worth unit testing. Usually this is an indicator that your code could be simplified. When you unit test code that is somewhat complex the tests can become a burden and normally it would be better to do an integration of edge-to-edge test in these cases. It's also not clear in your example how your class gets the RequestStack so I will assume that it has been injected in __construct.
With that said here's how you would test that code:
protected function setUp()
{
$this->requestStack = $this->getMock('Fully-qualified RequestStack namespace');
$this->SUT = new MyClass($this->requestStack);
}
/** #test */
public function it_should_store_value_in_the_session()
{
$value = 'test value';
$request = $this->getMock('Request');
$request->request = $this->getMock('ParameterBag');
$session = $this->getMock('Session');
$this->requestStack
->expects($this->atLeastOnce())
->method('getCurrentRequest')
->will($this->returnValue());
$request->request
->expects($this->atLeastOnce())
->method('get')
->with('something')
->will($this->returnValue($value));
$request
->expects($this->once())
->method('getSession')
->will($this->returnValue($session));
$session
->expects($this->once())
->method('set')
->with('somevar', $value);
$this->SUT->doSomething();
}
This should give you a starting point but beware having a wall-of mocks in your tests because very small changes to the implementation details can cause your tests to fail even though the behaviour is still correct and this is something you want to avoid as much as possible so the tests aren't expensive to maintain.
Edit: I thought some more about your question and realized that typically you can inject the Session as a dependency. If that's possible in your use case it would simplify the tests a lot.
You don't need to mock RequestStack, it's a super simple class. You can just create a fake request and push it to it. You can also mock the session.
// you can overwrite any value you want through the constructor if you need more control
$fakeRequest = Request::create('/', 'GET');
$fakeRequest->setSession(new Session(new MockArraySessionStorage()));
$requestStack = new RequestStack();
$requestStack->push($fakeRequest);
// then pass the requestStack to your service under test.
But in terms of testing, having to mess around with the internals of a class is not a good sign. Maybe you can create a handler class to encapsulate the logic you need from the request stack so you can test more easily.
It's difficult to imagine a situation where you'd have to be dealing with GET/POST parameters inside a unit-tested class. Have the Controller deal with HTTP requests and sessions (that's pretty much what they're there for), and pass the variables down into the relevant classes to deal with the rest.
That being said, Kevin's response is a possible solution if you want to go down that route.
According to this: http://api.symfony.com/2.4/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.html
I got to work something like the following:
public function testCompanySession()
{
$Request = new Request();
$Request->setSession(
new Session(new MockArraySessionStorage())
);
$CompanySessionMapper = new CompanyMapper($Request);
$Company = new Company();
$Company->setName('test');
$CompanySessionMapper->set($Company);
$Company = new Company();
$CompanySessionMapper->get($Company);
$this->assertEquals($Company->getName(), 'test');
}
Only one test per object type in my case since I'm only testing if the session name is correct and retrieving/storing the object properly in the session. CompanyMapper class uses the session to store the company object among other session/application related functions.
Anyone coming from Google like me wants to know how to mock request content, it is as simple as:
use AppBundle\Controller\DefaultController;
use Symfony\Component\HttpFoundation\Request;
use PHPUnit\Framework\TestCase;
class DefaultControllerTest extends TestCase
{
//#dataProvider?
public function testWithMockedRequest()
{
//create a request mock
$request = $this
->getMockBuilder(Request::class)
->getMock();
//set the return value
$request
->expects($this->once())
->method('getContent')
->will($this->returnValue('put your request data here'));
//create your controller
$controller = new DefaultController();
//get the Response based on your Request
$response = $controller->myRequestAction($request);
//assert!
$this->assertEquals(200, $response->getStatusCode());
}
}
As you can see you can execute a real controller which uses $request->getContent()
I hope this helps someone.
I want a variable bound to the application scope, (in java that would be application scope).
I thought service should be the thing to use in order to reach my goal.
I created a service test
<?php
namespace Acme\MyBundle\Service;
class test {
public $count;
public function __construct() {
$this->count = 0;
}
public function addCount() {
$this->count++;
}
}
which I declared in services.yml
acme.my.service.test:
class: Acme\MyBundle\Service\test
I call it in my controller
public function testAction() {
$this->get('acme.my.service.test')->addCount();
return $this->render('AcmeMyBundle:Test:test.html.twig');
}
But when I retrieve it in my twig, the value is 1, no matter how much I refresh or go with multiple session on the url bound to my testAction.
=> It means that constructor is called each time.
So is that the right way to do? I thought services were created once and reused then, but I may be mistaken.
Could you please enlighten me?
Thank you,
copndz
I found what I was looking for, APC caching system.
Easy to use and integrated to doctrine common.
How to cache in Symfony 2?
http://docs.doctrine-project.org/en/latest/reference/caching.html
I have a controller I'd like to create functional tests for. This controller makes HTTP requests to an external API via a MyApiClient class. I need to mock out this MyApiClient class, so I can test how my controller responds for given responses (e.g. what will it do if the MyApiClient class returns a 500 response).
I have no problems creating a mocked version of the MyApiClient class via the standard PHPUnit mockbuilder: The problem I'm having is getting my controller to use this object for more than one request.
I'm currently doing the following in my test:
class ApplicationControllerTest extends WebTestCase
{
public function testSomething()
{
$client = static::createClient();
$apiClient = $this->getMockMyApiClient();
$client->getContainer()->set('myapiclient', $apiClient);
$client->request('GET', '/my/url/here');
// Some assertions: Mocked API client returns 500 as expected.
$client->request('GET', '/my/url/here');
// Some assertions: Mocked API client is not used: Actual MyApiClient instance is being used instead.
}
protected function getMockMyApiClient()
{
$client = $this->getMockBuilder('Namespace\Of\MyApiClient')
->setMethods(array('doSomething'))
->getMock();
$client->expects($this->any())
->method('doSomething')
->will($this->returnValue(500));
return $apiClient;
}
}
It seems as though the container is being rebuilt when the second request is made, causing the MyApiClient to be instantiated again. The MyApiClient class is configured to be a service via an annotation (using the JMS DI Extra Bundle) and injected into a property of the controller via an annotation.
I'd split each request out into its own test to work around doing this if I could, but unfortunately I can't: I need to make a request to the controller via a GET action and then POST the form it brings back. I'd like to do this for two reasons:
1) The form uses CSRF protection, so if I just POST directly to the form without using the crawler to submit it, the form fails the CSRF check.
2) Testing that the form generates the correct POST request when it is submitted is a bonus.
Does anyone have any suggestions on how to do this?
EDIT:
This can be expressed in the following unit test that does not depend on any of my code, so may be clearer:
public function testAMockServiceCanBeAccessedByMultipleRequests()
{
$client = static::createClient();
// Set the container to contain an instance of stdClass at key 'testing123'.
$keyName = 'testing123';
$client->getContainer()->set($keyName, new \stdClass());
// Check our object is still set on the container.
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.
$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.
$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Fails.
}
This test fails, even if I call $client->getContainer()->set($keyName, new \stdClass()); immediately before the second call to request()
When you call self::createClient(), you get a booted instance of the Symfony2 kernel. That means, all config is parsed and loaded. When now sending a request, you let the system do it's job for the first time, right?
After the first request, you may want to check what went on, and therefore, the kernel is in a state, where the request is sent, but it's still running.
If you now run a second request, the web-architecture requires, that the kernel reboots, because it already ran a request. This reboot, in your code, is executed, when you execute a request for the second time.
If you want to boot the kernel and modify it before the request is sent to it (like you want), you have to shutdown the old kernel-instance and boot a fresh one.
You can do that by just rerunning self::createClient(). Now you again have to apply your mock, as you did the first time.
This is the modified code of your second example:
public function testAMockServiceCanBeAccessedByMultipleRequests()
{
$keyName = 'testing123';
$client = static::createClient();
$client->getContainer()->set($keyName, new \stdClass());
// Check our object is still set on the container.
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));
$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));
# addded these two lines here:
$client = static::createClient();
$client->getContainer()->set($keyName, new \stdClass());
$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));
}
Now you may want to create a separate method, that mocks the fresh instance for you, so you don't have to copy your code ...
I thought I'd jump in here. Chrisc, I think what you want is here:
https://github.com/PolishSymfonyCommunity/SymfonyMockerContainer
I agree with your general approach, configuring this in the service container as a parameter is really not a good approach. The whole idea is to be able to mock this dynamically during individual test runs.
The behaviour you are experiencing is actually what you would experience in any real scenario, as PHP is share nothing and rebuilds the whole stack on each request. The functional test suite imitates this behaviour to not generate wrong results. One example would be doctrine, which has a ObjectCache, so you could create objects, not save them to the database and your tests would all pass because it takes the objects out of the cache all the time.
You can solve this problem in different ways:
Create a real class which is a TestDouble and emulates the results you would expect from the real API. This is actually very easy: You create a new MyApiClientTestDouble with the same signature as your normal MyApiClient, and just change the method bodies where needed.
In your service.yml, you alright might have this:
parameters:
myApiClientClass: Namespace\Of\MyApiClient
service:
myApiClient:
class: %myApiClientClass%
If this is the case, you can easily overwrite which class is taken by adding the following to your config_test.yml:
parameters:
myApiClientClass: Namespace\Of\MyApiClientTestDouble
Now the service container will use your TestDouble when testing. If both classes have the same signature, nothing more is needed. I don't know if or how this works with the DI Extras Bundle. but I guess there is a way.
Or you could create a ApiDouble, implementing a "real" API which behaves in the same way your external API does but returns test data. You would then make the URI of your API handled by the service container (e.g. setter injection) and create a parameters variable which points to the right API (the test one in case of dev or test and the real one in case of the production environment).
The third way is a bit hacky, but you can always make a private method inside your tests request which first sets up the container in the right way and then calls the client to make the request.
I do not know if you ever found out how to fix your problem. But here is the solution i used. This is also good for other people finding this.
After a long search for the problem with mocking a service between multiple client requests i found this blog post:
http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html
lyrixx talk about how the kernel shutsdown after each request making the service overrid invalid when you try to make another request.
To fix this he creates a AppTestKernel used only for the function tests.
This AppTestKernel extends the AppKernel and only apply some handlers to modifie the Kernel:
Code examples from lyrixx blogpost.
<?php
// app/AppTestKernel.php
require_once __DIR__.'/AppKernel.php';
class AppTestKernel extends AppKernel
{
private $kernelModifier = null;
public function boot()
{
parent::boot();
if ($kernelModifier = $this->kernelModifier) {
$kernelModifier($this);
$this->kernelModifier = null;
};
}
public function setKernelModifier(\Closure $kernelModifier)
{
$this->kernelModifier = $kernelModifier;
// We force the kernel to shutdown to be sure the next request will boot it
$this->shutdown();
}
}
When you then need to override a service in your test you call the setter on the testAppKernel and applies the mock
class TwitterTest extends WebTestCase
{
public function testTwitter()
{
$twitter = $this->getMock('Twitter');
// Configure your mock here.
static::$kernel->setKernelModifier(function($kernel) use ($twitter) {
$kernel->getContainer()->set('my_bundle.twitter', $twitter);
});
$this->client->request('GET', '/fetch/twitter'));
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
}
}
After following this guide i had some problems getting the phpunittest to startup with the new AppTestKernel.
I found out that the symfonys WebTestCase (https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php)
Takes the first AppKernel file it finds. So one way to get out of this is to change the name on the AppTestKernel to come before AppKernel or to override the method to take the TestKernel Instead
Here i overrride the getKernelClass in the WebTestCase to look for a *TestKernel.php
protected static function getKernelClass()
{
$dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
$finder = new Finder();
$finder->name('*TestKernel.php')->depth(0)->in($dir);
$results = iterator_to_array($finder);
if (!count($results)) {
throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.');
}
$file = current($results);
$class = $file->getBasename('.php');
require_once $file;
return $class;
}
After this your tests will load with the new AppTestKernel and you will be able to mock services between multiple client requests.
Based on the answer by Mibsen you can also set this up in a similar way by extending the WebTestCase and overriding the createClient method. Something along these lines:
class MyTestCase extends WebTestCase
{
private static $kernelModifier = null;
/**
* Set a Closure to modify the Kernel
*/
public function setKernelModifier(\Closure $kernelModifier)
{
self::$kernelModifier = $kernelModifier;
$this->ensureKernelShutdown();
}
/**
* Override the createClient method in WebTestCase to invoke the kernelModifier
*/
protected static function createClient(array $options = [], array $server = [])
{
static::bootKernel($options);
if ($kernelModifier = self::$kernelModifier) {
$kernelModifier->__invoke();
self::$kernelModifier = null;
};
$client = static::$kernel->getContainer()->get('test.client');
$client->setServerParameters($server);
return $client;
}
}
Then in the test you would do something like:
class ApplicationControllerTest extends MyTestCase
{
public function testSomething()
{
$apiClient = $this->getMockMyApiClient();
$this->setKernelModifier(function () use ($apiClient) {
static::$kernel->getContainer()->set('myapiclient', $apiClient);
});
$client = static::createClient();
.....
Make a mock:
$mock = $this->getMockBuilder($className)
->disableOriginalConstructor()
->getMock();
$mock->method($method)->willReturn($return);
Replace service_name on mock-object:
$client = static::createClient()
$client->getContainer()->set('service_name', $mock);
My problem was to use:
self::$kernel->getContainer();
I faced with the same problem in Symfony 4.4.
After reading
Create mocks in api functional testing with Symfony
I found a solution - self::ensureKernelShutdown()
...
$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.
self::ensureKernelShutdown()
$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.
...
I've just started to use Jersey to create a RESTful API for my site. Its a wonderful change from having to roll my own support for RESTful services in Java. One thing I just can't seem to figure out is how to "fake" a DELETE and PUT method.
Jersey supports the annotations #PUT and #DELETE, however many Load-Balancers will not allow these methods through. In the past I've relied on the ability to define a custom HTTP header (e.g. x-method-override: DELETE) and "tunneling" within a POST request.
Has anyone found a way to bind a method using Jersey/JAX-RS annotations to custom headers? Alternatively, is there a better way around lack of support for PUT and DELETE?
Well here is how I've decided to handle the situation within my API. Its relatively simple and doesn't require much additional coding. To illustrate consider a RESTful api for Address:
#Path("/address")
public class AddressService {
#GET
#Produces("application/xml")
public StreamingOutput findAll() { ... }
#POST
#Produces("application/xml")
#Consumes("application/x-www-form-urlencoded")
public StreamingOutput create(...) { ... }
//
// This is the alternative to a "PUT" method used to indicate an "Update"
// action. Notice that the #Path expects "/id/{id}" which allows
// us to bind to "POST" and not get confused with a "Create"
// action (see create() above).
//
#POST
#Produces("application/xml")
#Consumes("application/x-www-form-urlencoded")
#Path("/id/{id}")
public StreamingOutput update(#PathParam("id") Long id, ...) { ... }
//
// This is the typical "GET" method with the addition of a check
// for a custom header "x-method-override" which is designed to
// look for inbound requests that come in as a "GET" but are
// intended as "DELETE". If the methodOverride is set to "DELETE"
// then the *real* delete() method is called (See below)
//
#GET
#Produces("application/xml")
#Path("/id/{id}")
public StreamingOutput retrieve(
#PathParam("id") Long id,
#HeaderParam("x-method-override") String methodOverride)
{
if (methodOverride != null && methodOverride.equalsIgnoreCase("DELETE")) {
this.delete(id);
}
...
}
//
// This is the typical "DELETE" method. The onlything special about it is that
// it may get invoked by the #GET equivalent is the "x-method-override" header
// is configured for "DELETE"
//
#DELETE
#Produces("application/xml")
#Path("/id/{id}")
public StreamingOutput retrieve(#PathParam("id") Long id) { ... }
}
It's not really REST anymore, but in a similar situation we defined POST /collection/ to be insert (as normal), POST /collection/{id} to be update, POST /collection/{id} without body to be delete.