Mocking Symfony2's request and session in PHPUnit - symfony

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.

Related

Injecting an arbitrary parameter to a service

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.

Api-Platform: using PUT for creating resources

I would like to use the PUT method for creating resources. They are identified by an UUID, and since it is possible to create UUIDs on the client side, I would like to enable the following behaviour:
on PUT /api/myresource/4dc6efae-1edd-4f46-b2fe-f00c968fd881 if this resource exists, update it
on PUT /api/myresource/4dc6efae-1edd-4f46-b2fe-f00c968fd881 if this resource does not exist, create it
It's possible to achieve this by implementing an ItemDataProviderInterface / RestrictedDataProviderInterface.
However, my resource is actually a subresource, so let's say I want to create a new Book which references an existing Author.
My constructor looks like this:
/**
* Book constructor
*/
public function __construct(Author $author, string $uuid) {
$this->author = $author;
$this->id = $uuid;
}
But I don't know how to access the Author entity (provided in the request body) from my BookItemProvider.
Any ideas?
In API Platform many things that should occur on item creation is based on the kind of request it is. It would be complicated to change.
Here are 2 possibilities to make what you want.
First, you may consider to do a custom route and use your own logic. If you do it you will probably be happy to know that using the option _api_resource_class on your custom route will enable some listeners of APIPlaform and avoid you some work.
The second solution, if you need global behavior for example, is to override API Platform. Your main problem for this is the ReadListener of ApiPlatform that will throw an exception if it can't found your resource. This code may not work but here is the idea of how to override this behavior:
class CustomReadListener
{
private $decoratedListener;
public function __construct($decoratedListener)
{
$this->decoratedListener = $decoratedListener;
}
public function onKernelRequest(GetResponseEvent $event)
{
try {
$this->decoratedListener->onKernelRequest($event);
} catch (NotFoundHttpException $e) {
// Don't forget to throw the exception if the http method isn't PUT
// else you're gonna break the 404 errors
$request = $event->getRequest();
if (Request::METHOD_PUT !== $request->getMethod()) {
throw $e;
}
// 2 solutions here:
// 1st is doing nothing except add the id inside request data
// so the deserializer listener will be able to build your object
// 2nd is to build the object, here is a possible implementation
// The resource class is stored in this property
$resourceClass = $request->attributes->get('_api_resource_class');
// You may want to use a factory? Do your magic.
$request->attributes->set('data', new $resourceClass());
}
}
}
And you need to specify a configuration to declare your class as service decorator:
services:
CustomReadListener:
decorate: api_platform.listener.request.read
arguments:
- "#CustomReadListener.inner"
Hope it helps. :)
More information:
Information about event dispatcher and kernel events: http://symfony.com/doc/current/components/event_dispatcher.html
ApiPlatform custom operation: https://api-platform.com/docs/core/operations#creating-custom-operations-and-controllers
Symfony service decoration: https://symfony.com/doc/current/service_container/service_decoration.html

Proper way to test a Symfony2 Service with Doctrine

I'm struggling to find the correct way to unit test my symfony 2 services which use doctrine or other common services.
What i have done so far:
In my understanding the controller actions should:
be as short as possible
take the request
execute required methods from injected services
build a response out of this
is a service itself
To accomplish a lightweight action, i try to encapsule the logic into a separate service which gets injected into the controller.
This works nicely expect for testing everything.
Here my current code:
Controller
class SearchController
{
// search_helper, request and templating are controller-injected
protected $search_helper;
protected $request;
protected $templating;
// ...
public function searchAction()
{
$searchterm = strtolower($this->request->query->get('q'));
$result = $this->search_helper->findSamples($searchterm);
// Found a single result. Redirect to this page
if (is_string($result))
{
return new RedirectResponse($result, 301);
}
return new Response($this->templating->render('AlbiSampleBundle:Search:index.html.twig', array('results' => $result)));
}
}
SearchService
class SearchHelper
{
// doctrine, session and min_query_len are controller-injected
protected $doctrine;
protected $session;
protected $min_query_len;
// ...
public function findSamples($searchterm)
{
if (strlen($searchterm) < $this->min_query_len)
{
$msg = 'Your search must contain at least 3 characters!';
$this->session->getFlashBag()->add('error', $msg);
return false;
}
$em = $this->doctrine->getManager();
$results = $em->getRepository('AlbiSampleBundle:Sample')->findPossibleSamples($searchterm);
// Execute a more advanced search, if std. search don't delivers a result
// ...
return $results;
}
}
How can i test this code correctly?
The repository is tested with phpunit_db and a inmemory sqlite database ✓
The action can be tested through a simple functional test ✓
What's left is the logic in the search-service. e.g. the findSamples method
My first thought was to mock the dependencies (in fact that was one of the main aspects in separating the dependencies), but you not only have to mock the doctrine object, but also the entitymanager and the repository.
$em = $this->doctrine->getManager();
$results = $em->getRepository('AlbiSampleBundle:Sample')->findPossibleSamples($searchterm);
I think there must be a better solution. Not only would this mocking need many LOCs, it also doesn't feel right. The test would be unnecessarily coupled really tight to the SUT.
EDIT
Here is a sample test i came up with. Using mock objects.
The test won't work. I realized it would take much more mock-objects and i got the feeling this isn't the right way.
The test fails because SessionMock->getFlashbag doesn't return a flashbag with add method.
doctrine->getManager returns no EntityManager. The EntityManager has no getRepository method. And the repository is missing findPossibleSamples.
class SearchHelperTest extends \PHPUnit_Framework_TestCase
{
private $router;
private $session;
private $doctrine;
public function setUp()
{
parent::setUp();
// ...
}
public function testSearchReturnValue()
{
$search_service = $this->createSearchHelper();
$this->assertFalse($search_service->findSamples('s'));
}
protected function createSearchHelper()
{
return new SearchHelper($this->doctrine, $this->router, $this->session, 3);
}
protected function getDoctrineMock()
{
return $this->getMock('Doctrine\Bundle\DoctrineBundle\Registry', array('getManager'), array(), '', false);
}
protected function getSessionMock()
{
return $this->getMock('Symfony\Component\HttpFoundation\Session\Session', array('getFlashBag'), array(), '', false);
}
protected function getRouterMock()
{
return $this->getMock('Symfony\Component\Routing\Router', array('generate'), array(), '', false);
}
}
Hope the community can help me, writing well tested code :)
cheers
For your specific example I would argue that the validation of the $searchterm doesn't really belong in your service - at the very least a service should never depend on the session. There are ways you could move the session out of the service and leave the validation in but personally I would use symfony validation for this i.e. have a SampleSearchType for the form that uses itself as the data class and hang the validation off that in validation.yml (or using annotations as appropriate).
Once that validation is taken out, what's left from your question is another findX() method to be added to the repository (there's no reason why repository methods can't call and build on each other) which you already know how to test.
Having said that, I still agree that with Symfony there is a general issue of how to test services in isolation from injected services. With respect to testing in isolation from the persistence layer I've avoiding trying to do this so far. My business layer services are so tightly coupled with the persistence layer that the cost of trying to test them independently is not worthwhile (what logic there is consists mainly of making related db updates or sending emails for which symfony provides it's own decoupling mechanism). I'm not sure if this is because I'm doing it wrong or because the apps I'm working on are light on business logic!
To isolate service tests from dependencies other than persistence I've tried:
Overriding service classes with mocked versions in the configuration. Issue - you don't want to do this for functional tests which means you have to have tests scripts which update the configuration and/or change the config to run individual tests. Advantage - you can run the same test as an isolated unit test and as an integration test by flipping the config
(Warning: nasty hack!) providing a setter method to replace an injected service with a mocked version from the test program.
(Not yet tried) Directly instantiate the service being tested, passing mock dependencies in on construction.
With respect to isolating from the persistence layer the only approach that makes sense to me is to abstract it out of the service to be tested into a wrapper service which contains no additional logic. The wrapper service could then be mocked using one of the above approaches (or hopefully a better solution that someone else is going to suggest?!)
EDIT: to address the issue of complexity of mocking dependencies - very occasionally this may be unavoidable but in general this is an indication that the design needs revisiting. This is one of the strengths of TDD - it strongly encourages simplified design and decoupling of components:
No service should need to be dependent upon the session object. This is not good practice and can always be avoided. Worst case the example method could return mixed values and if the result is not an array it's assumed to be an error message, although there are better alternatives.
Sometimes dependencies are unnecessary (code more naturally belongs elsewhere) or too general (I would question the necessity of injecting high level objects like doctrine or e.g. the container into anything other than test helpers).
If there is a complex dependency to mock (such as on multiple classes from the persistence layer) abstract it out into a wrapper which is far simpler to mock than the complex dependency.

Symfony 2 functional tests with mocked services

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.
...

Symfony2: Testing entity validation constraints

Does anyone have a good way to unit test an entity's validation constraints in Symfony2?
Ideally I want to have access to the Dependency Injection Container within the unit test which would then give me access to the validator service. Once I have the validator service I can run it manually:
$errors = $validator->validate($entity);
I could extend WebTestCase and then create a client to get to the container as per the docs however it doesn't feel right. The WebTestCase and client read in the docs as more of a facility to test actions as a whole and therefore it feels broken to use it to unit test an entity.
So, does anyone know how to either a) get the container or b) create the validator inside a unit test?
Ok since this got two votes I guess other people are interested.
I decided to get my shovel out and was pleasantly surprised (so far anyway) that this wasn't at all difficult to pull off.
I remembered that each Symfony2 component can be used in a stand alone mode and therefore that I could create the validator myself.
Looking at the docs at: https://github.com/symfony/Validator/blob/master/ValidatorFactory.php
I realised that since there was a ValidatorFactory it was trivial to create a validator (especially for validation done by annotations which I am, although if you look at the docblock on the page I linked above you'll also find ways to validate xml and yml).
First:
# Symfony >=2.1
use Symfony\Component\Validator\Validation;
# Symfony <2.1
use Symfony\Component\Validator\ValidatorFactory;
and then:
# Symfony >=2.1
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
# Symfony <2.1
$validator = ValidatorFactory::buildDefault()->getValidator();
$errors = $validator->validate($entity);
$this->assertEquals(0, count($errors));
I hope this helps anyone else whose conscience wouldn't allow them to just use WebTestCase ;).
We end up rolling your own base test case to access the dependency container from within a test case. Here the class in question:
<?php
namespace Application\AcmeBundle\Tests;
// This assumes that this class file is located at:
// src/Application/AcmeBundle/Tests/ContainerAwareUnitTestCase.php
// with Symfony 2.0 Standard Edition layout. You may need to change it
// to fit your own file system mapping.
require_once __DIR__.'/../../../../app/AppKernel.php';
class ContainerAwareUnitTestCase extends \PHPUnit_Framework_TestCase
{
protected static $kernel;
protected static $container;
public static function setUpBeforeClass()
{
self::$kernel = new \AppKernel('dev', true);
self::$kernel->boot();
self::$container = self::$kernel->getContainer();
}
public function get($serviceId)
{
return self::$kernel->getContainer()->get($serviceId);
}
}
With this base class, you can now do this in your test methods to access the validator service:
$validator = $this->get('validator');
We decided to go with a static function instead of the class constructor but you could easily change the behavior to instantiate the kernel into the constructor directly instead of relying on the static method setUpBeforeClass provided by PHPUnit.
Also, keep in mind that each single test method in you test case won't be isolated fro, each others because the container is shared for the whole test case. Making modification to the container may have impact on you other test method but this should not be the case if you access only the validator service. However, this way, the test cases will run faster because you will not need to instantiate and boot a new kernel for each test methods.
For the sake of reference, we find inspiration for this class from this blog post. It is written in French but I prefer to give credit to whom it belongs :)
Regards,
Matt
I liked Kasheens answer, but it doesn't work for Symfony 2.3 anymore.
There are little changes:
use Symfony\Component\Validator\Validation;
and
$validator = Validation::createValidatorBuilder()->getValidator();
If you want to validate Annotations for instance, use enableAnnotationMapping() like below:
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
the rest stays the same:
$errors = $validator->validate($entity);
$this->assertEquals(0, count($errors));
With Symfony 2.8, it seems that you can now use the AbstractConstraintValidatorTest class this way :
<?php
namespace AppBundle\Tests\Constraints;
use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
use AppBundle\Constraints\MyConstraint;
use AppBundle\Constraints\MyConstraintValidator;
use AppBundle\Entity\MyEntity;
use Symfony\Component\Validator\Validation;
class MyConstraintValidatorTest extends AbstractConstraintValidatorTest
{
protected function getApiVersion()
{
return Validation::API_VERSION_2_5;
}
protected function createValidator()
{
return new MyConstraintValidator();
}
public function testIsValid()
{
$this->validator->validate(null, new MyEntity());
$this->assertNoViolation();
}
public function testNotValid()
{
$this->assertViolationRaised(new MyEntity(), MyConstraint::SOME_ERROR_NAME);
}
}
You have got a good sample with the IpValidatorTest class
The answer in https://stackoverflow.com/a/41884661/4560833 has to be changed a little for Symfony 4:
Use ConstraintValidatorTestCase instead of AbstractConstraintValidatorTest.
Answer (b): Create the Validator inside the Unit Test (Symfony 2.0)
If you built a Constraint and a ConstraintValidator you don't need any DI container at all.
Say for example you want to test the Type constraint from Symfony and it's TypeValidator. You can simply do the following:
use Symfony\Component\Validator\Constraints\TypeValidator;
use Symfony\Component\Validator\Constraints\Type;
class TypeValidatorTest extends \PHPUnit_Framework_TestCase
{
function testIsValid()
{
// The Validator class.
$v = new TypeValidator();
// Call the isValid() method directly and pass a
// configured Type Constraint object (options
// are passed in an associative array).
$this->assertTrue($v->isValid(5, new Type(array('type' => 'integer'))));
$this->assertFalse($v->isValid(5, new Type(array('type' => 'string'))));
}
}
With this you can check every validator you like with any constraint configuration. You neither need the ValidatorFactory nor the Symfony kernel.
Update: As #psylosss pointed out, this doesn't work in Symfony 2.5. Nor does it work in Symfony >= 2.1. The interface from ConstraintValidator got changed: isValid was renamed to validate and doesn't return a boolean anymore. Now you need an ExecutionContextInterface to initialize a ConstraintValidator which itself needs at least a GlobalExecutionContextInterface and a TranslatorInterface... So basically it's not possible anymore without way too much work.
I don't see a problem with the WebTestCase. If you don't want a client, don't create one ;) But using a possibly different service than your actual application will use, that's a potential pit fall. So personally, I've done like this:
class ProductServiceTest extends Symfony\Bundle\FrameworkBundle\Test\WebTestCase
{
/**
* Setup the kernel.
*
* #return null
*/
public function setUp()
{
$kernel = self::getKernelClass();
self::$kernel = new $kernel('dev', true);
self::$kernel->boot();
}
public function testFoo(){
$em = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
$v = self::$kernel->getContainer()->get('validator');
// ...
}
}
It's less DRY than Matt answer -- as you'll repeat the code (for each test class) and boot the kernel often (for each test method), but it's self-contained and require no extra dependencies, so it depends on your needs. Plus I got rid of the static require.
Also, you're sure to have the same services that your application is using -- not default or mock, as you boot the kernel in the environnement that you wish to test.
If people still read this one in 2023, prefer to inject the ValidatorInterface for Symfony > 3 / 4.
use Symfony\Component\Validator\Validator\ValidatorInterface;
// ...
$this->validator->validate($myEntity);

Resources