I'm building a PHP application that needs to access Composer directly. In order to test the application, however, I don't actually want to run composer, so I'm trying to mock it.
<?php
namespace MyNamespace;
use Composer\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
class MyTest extends \PHPUnit_Framework_TestCase {
public function testComposer()
{
/** #var Application|\PHPUnit_Framework_MockObject_MockObject $composer */
$composer = $this->getMockForAbstractClass(Application::class);
$composer
->expects($this->any())
->method('run')
->will($this->returnValue(true));
$this->assertTrue(
$composer->run(new ArrayInput(['command' => 'install']))
);
}
}
This actually runs the code:
$ bin/phpunit -c phpunit-fast.xml tests/MyTest.php
PHPUnit 4.8.10 by Sebastian Bergmann and contributors.
Runtime: PHP 5.6.13-1+deb.sury.org~trusty+3 with Xdebug 2.3.2
Configuration: phpunit-fast.xml
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Generating autoload files
I've tried various combinations of ->getMock (which completely fails) and ->getMockBuilder but it always seems to actually use the ->run method rather than the stub.
I assume it is somehow replacing these method inside itself, but if that's the case, how to I prevent it?
Update
It was asked as to why I use getMockForAbstractClass rather than just getMock. When using getMock I get the following output:
PHPUnit 4.8.10 by Sebastian Bergmann and contributors.
Runtime: PHP 5.6.13-1+deb.sury.org~trusty+3 with Xdebug 2.3.2
Configuration: phpunit.xml.dist
E
Time: 1.19 minutes, Memory: 4.50Mb
There was 1 error:
1) MyNamespace\MyTest::testComposer
PHPUnit_Framework_MockObject_RuntimeException: Cannot mock Symfony\Component\Console\Application::setDispatcher() because a class or interface used in the signature is not loaded
tests/MyTest.php:22
Caused by
ReflectionException: Class Symfony\Component\EventDispatcher\EventDispatcherInterface does not exist
tests/MyTest.php:22
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
This is despite the fact that just using $composer = new Application(); works fine. In fact, if I add that line above the test, it still claims that an class or interface wasn't loaded, despite the object being instantiated correctly earlier.
I have 3 solutions to this:
1. Add Event Dispatcher yourself
Add "symfony/event-dispatcher" to your own require-dev
"require-dev" : {
...
"symfony/event-dispatcher" : "^2.1"
}
With the corrected test:
<?php
/**
* MyTest.php
*/
namespace MyNamespace;
use Composer\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
class MyTest extends \PHPUnit_Framework_TestCase {
public function testComposer()
{
/** #var Application|\PHPUnit_Framework_MockObject_MockObject $composer */
$composer = $this->getMock(Application::class);
$composer
->expects($this->any())
->method('run')
->will($this->returnValue(true));
$this->assertTrue(
$composer->run(new ArrayInput(['command' => 'install']))
);
}
}
Personal note: This feels like a dirty hack but is by far the easiest solution
2. Use Prophecy
Use prophecy alongside PHPUnit to mock the console.
"require-dev" : {
...
"phpspec/prophecy": "~1.0"
}
Now the test looks like this:
<?php
/**
* MyTest.php
*/
namespace MyNamespace;
use Composer\Console\Application;
use Prophecy\Prophet;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\Console\Input\ArrayInput;
class MyTest extends \PHPUnit_Framework_TestCase {
public function testComposer()
{
$prophet = new Prophet();
$composerProphecy = $prophet->prophesize(Application::class);
$composerProphecy
->run(new ArrayInput(['command' => 'install']))
->willReturn(true);
/** #var Application $composer */
$composer = $composerProphecy->reveal();
$this->assertTrue(
$composer->run(new ArrayInput(['command' => 'install']))
);
}
}
Personal note: I'm not keen on telling the prophecy what methods will be called using magic methods as these upset my IDE.
3. Use Mockery
Another option for the mocking system.
"require-dev" : {
...
"mockery/mockery": "^0.9.4"
}
And the test:
<?php
/**
* MyTest.php
*/
namespace MyNamespace;
use Composer\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
class MyTest extends \PHPUnit_Framework_TestCase {
public function testComposer()
{
/** #var Application|\Mockery\MockInterface $composer */
$composer = \Mockery::mock(Application::class);
$composer
->shouldReceive('run')
->with(ArrayInput::class)
->andReturn(true);
$this->assertTrue(
$composer->run(new ArrayInput(['command' => 'install']))
);
\Mockery::close();
}
}
Personal note: Static usage, having to remember to clean up, and a mis-documented use of a variadic in shouldReceive made me very sad.
4. (Bonus) Fix Symfony Console
Seems unlikely but if anyone can work out how to fix #8200, then that would mean no one would have to use multiple mock frameworks (if you're already using PHPUnit anyway) or add dirty hacks to their require-dev for a single broken test.
I can't comment yet, sorry. :/
TL;DR: getMock variant works for me. Check your environment.
Getting composer via composer (in composer.json require "composer/composer": "#dev") makes the test (using getMock) pass for me.
Check which version of symfony you're using (mine is 2.7.4) and if you're using the phar version of composer (which you probably shouldn't).
Related
When using the #covers-annotation, it's not been taken into account when displaying the code coverage.
No matter if I just "cover" the class, or the method, or both.
Test:
/**
* #covers \App\Controller\UserEditController
*/
class UserEditWebTest extends WebTestCase
{
/**
* #covers ::edit
*/
public function testEdit():void
{
//...
$this->assertSomething();
}
}
And the tested Controller:
namespace App\Controller;
class UserEditController
{
public function edit() {
//..
}
protected function someOtherFn():void
{
//..
}
}
Now UnitTest says on code-coverate output:
$ utest --coverage-text
Classes: 23.41% (125/534)
Methods: 32.75% (1091/3331)
Lines: 31.58% (4799/15198)
App\Controller\Admin\User\UserEditController
Methods: 0.00% ( 0/ 2) Lines: 44.78% ( 30/ 67)
Q: What am I doing wrong?
Why is Methods on 0/2 ?
I also tried removing the #covers-Annotation from the test-method docblock, or adding the full namespace of the controller before the ::edit - nothing works.
I cleared the cache of the test-environment
Solution
I found out, that using the #covers-annotation is not required at all, actually. If everything is configured correctly, the tested classes, methods and lines should automatically being registered correctly.
In PhpStorm, you can run a "coverage test" which is an xdebug run that shows all code covered.
The annotation was not been taken into account, so I had to actually change my test so that more of the code is truly being tested - e.g. adding tests for each possible case inside a method.
I am migrating our project to Symfony 4. In my test suites, we used PHPUnit for functional tests (I mean, we call endpoints and we check result). Often, we mock services to check different steps.
Since I migrated to Symfony 4, I am facing this issue: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "my.service" service is already initialized, you cannot replace it.
when we redefine it like this : static::$container->set("my.service", $mock);
Only for tests, how can I fix this issue?
Thank you
Replacing is deprecated since Symfony 3.3. Instead of replacing service you should try using aliases.
http://symfony.com/doc/current/service_container/alias_private.html
Also, you can try this approach:
$this->container->getDefinition('user.user_service')->setSynthetic(true);
before doing $container->set()
Replace Symfony service in tests for php 7.2
Finally, I found a solution. Maybe not the best, but, it's working:
I created another test container class and I override the services property using Reflection:
<?php
namespace My\Bundle\Test;
use Symfony\Bundle\FrameworkBundle\Test\TestContainer as BaseTestContainer;
class TestContainer extends BaseTestContainer
{
private $publicContainer;
public function set($id, $service)
{
$r = new \ReflectionObject($this->publicContainer);
$p = $r->getProperty('services');
$p->setAccessible(true);
$services = $p->getValue($this->publicContainer);
$services[$id] = $service;
$p->setValue($this->publicContainer, $services);
}
public function setPublicContainer($container)
{
$this->publicContainer = $container;
}
Kernel.php :
<?php
namespace App;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function getOriginalContainer()
{
if(!$this->container) {
parent::boot();
}
/** #var Container $container */
return $this->container;
}
public function getContainer()
{
if ($this->environment == 'prod') {
return parent::getContainer();
}
/** #var Container $container */
$container = $this->getOriginalContainer();
$testContainer = $container->get('my.test.service_container');
$testContainer->setPublicContainer($container);
return $testContainer;
}
It's really ugly, but it's working.
I've got a couple of tests like this (the real code performs some actions and returns a result, the test-version just returns false for every answer).
If you create and use a custom config for each environment (eg: a services_test.yaml, or in Symfony4 probably tests/services.yaml), and first have it include dev/services.yaml, but then override the service you want, the last definition will be used.
app/config/services_test.yml:
imports:
- { resource: services.yml }
App\BotDetector\BotDetectable: '#App\BotDetector\BotDetectorNeverBot'
# in the top-level 'live/prod' config this would be
# App\BotDetector\BotDetectable: '#App\BotDetector\BotDetector'
Here, I'm using an Interface as a service-name, but it will do the same with '#service.name' style as well.
As I understood it, it means that class X was already injected(because of some other dependency) somewhere before your code tries to overwrite it with self::$container->set(X:class, $someMock).
If you on Symfony 3.4 and below you can ovverride services in container regardless it privite or public. Only deprication notice will be emmited, with content similar to error message from question.
On Symfony 4.0 error from the question was thown.
But on Symfony 4.1 and above you can lean on special "test" container. To learn how to use it consider follow next links:
https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing
https://dev.to/nikolastojilj12/symfony-5-mocking-private-autowired-services-in-controller-functional-tests-24j4
I'm just test playing with Php unit.
Here is my DependencyFailureTest class:
require_once '../vendor/autoload.php';
use PHPUnit\Framework\TestCase;
class DependencyFailureTest extends \PHPUnit\Framework\TestCase
{
public function testOne()
{
$this->assertTrue(false);
}
/**
* #depends testOne
*/
public function testTwo()
{
}
}
But on running the command phpunit --verbose DependencyFailureTest it throws
Argument #3 (No Value) of PHPUnit_TextUI_ResultPrinter::__construct() must be a value from "never", "auto" or "always".
Can anybody give an explanation for this issue?
It must be a configuration issue. I copied your code and ran it on the command line with verbose and it worked fine with version 5.4.6.
I would reinstall phpunit and ensure you have the latest version.
Also, their sample test case from their Getting Started page is:
<?php
use PHPUnit\Framework\TestCase;
class MoneyTest extends TestCase
{
// ...
public function testCanBeNegated()
{
// Arrange
$a = new Money(1);
// Act
$b = $a->negate();
// Assert
$this->assertEquals(-1, $b->getAmount());
}
// ...
}
https://phpunit.de/getting-started.html
Notice the difference in your extension usage, although I don't think it is an issue, if you use their declaration as stated, it helps to isolate the problem.
I ran into this. Was passing --colors=true, but that is incorrect.
I am new to Symfony, I have wrote small app now have to add unit tests, here is my controller:
<?php
namespace myBundle\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
class IndexController extends AbstractController
{
/**
* #param \Symfony\Component\HttpFoundation\Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
public function indexAction(Request $request)
{
if ($this->getRequest()->isMethod('POST')) {
// Do Something
}
// Do Something otherwise
}
}
My test:
class IndexControllerTest extends \PHPUnit_Framework_TestCase
{
protected $testController;
public function setUp()
{
$this->testController =
$this->getMockBuilder('myBundle\Controller\IndexController')
->disableOriginalConstructor()
->getMock();
}
public function testPostSaveActionWithBadRequest()
{
$expectation = 'Some text ';
$response = $this->testController->indexAction(new Request);
$this->assertInstanceOf(
'Symfony\Component\HttpFoundation\JsonResponse',
$response
);
$content = json_decode($response->getContent());
$this->assertEquals($expectation, $content->error);
}
}
When I run this test I get following:
PHP Fatal error: Call to a member function get()
which is basically on following line
if ($this->getRequest()->isMethod('POST')) {
this tells me the container is null (I verified it by printing dump of the container).
any idea what am I missing here or is there a way to provide container as dependency for that test.
I really appreciate all the help.
thanks
FI
You're trying to mock the class you're suppose to test:
$this->testController =
$this->getMockBuilder('myBundle\Controller\IndexController')
->disableOriginalConstructor()
->getMock();
You should actually instantiate the class you're testing, and mock or stub its collaborators.
However, in this particular scenario, instead of writing a unit test, write a functional test. There's a chapter on writing functional tests in Symfony docs that'll help you.
Your controller uses lots of framework classes (classes that don't belong to you), and you shouldn't mock them either. That's why functional tests are better in this case. Also, make sure you move as much code as possible out of your controller, so you can properly unit test that part (and write as little functional tests as possible).
In the meantime read some books on unit testing (in the following order):
TDD by Example
Growing Object-Oriented Software Guided by Tests
I am attempting to use JMSSerializer as a stand alone library to map JSON responses from an API to my model classes and am running into some issues.
Executing the following code results in an exception:
<?php
require dirname(__DIR__) . '/vendor/autoload.php';
use JMS\Serializer\Annotation AS JMS;
class Trii {
/**
* User ID for this session
* #JMS\SerializedName("userID")
* #JMS\Annotation(getter="getUserId")
* #JMS\Type("string")
* #var string
*/
private $userId;
public function getUserId() {
return $this->userId;
}
public function setUserId($userId) {
$this->userId = $userId;
}
}
$serializer = \JMS\Serializer\SerializerBuilder::create()->setDebug(true)->build();
$object = $serializer->deserialize('{"userID":"Trii"}', 'Trii', 'json');
var_dump($object);
?>
Here is the exception
Doctrine\Common\Annotations\AnnotationException: [Semantical Error] The annotation "#JMS\Serializer\Annotation\SerializedName" in property Trii::$userId does not exist, or could not be auto-loaded.
I have the following libraries installed for the project via composer
{
"require": {
"jms/serializer": "1.0.*#dev"
}
}
Is there something obvious I am missing since I am not using the whole Doctrine 2 solution?
EDIT: my final solution was to create a bootstrap file with the following content:
<?php
// standard composer install vendor autoload magic
require dirname(__DIR__) . '/vendor/autoload.php';
// Bootstrap the JMS custom annotations for Object to Json mapping
\Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
'JMS\Serializer\Annotation',
dirname(__DIR__).'/vendor/jms/serializer/src'
);
?>
Pretty sure this enables silent auto-loading which is much more convenient than registering the namespaces yourself.
AnnotationRegistry::registerLoader('class_exists');
I ran into the same problem and found your question through Google. Unfortunately you hadn't yet received any answers, so I had to dig in myself. :P
The thing is, Doctrine Annotations, which JMSSerializer Annotations uses, does NOT use normal PHP autoloading.
How are these annotations loaded? From looking at the code you could guess that the ORM Mapping, Assert Validation and the fully qualified annotation can just be loaded using the defined PHP autoloaders. This is not the case however: For error handling reasons every check for class existence inside the AnnotationReader sets the second parameter $autoload of class_exists($name, $autoload) to false. To work flawlessly the AnnotationReader requires silent autoloaders which many autoloaders are not. Silent autoloading is NOT part of the PSR-0 specification for autoloading.
This means you have to register the Annotation file(s) yourself:
AnnotationRegistry::registerFile(
<PROJECT ROOT> .
"/vendor/jms/serializer/src/JMS/Serializer/Annotation/SerializedName.php");
... or the whole namespace (preferred method):
AnnotationRegistry::registerAutoloadNamespace(
'JMS\Serializer\Annotation',
<PROJECT ROOT> . "/vendor/jms/serializer/src");
Be careful with the path in registerAutoloadNamespace. I first tried to register the whole path to annotations in the same manner with registerFile:
<PROJECT ROOT> . "/vendor/jms/serializer/src/JMS/Serializer/Annotation
but that failed miserably. :D
I hope this gets you a step further. :)
#SirArturio has the correct answer to this Autoloading puzzle, and I just wanted to add a touch more clarity in response to #messified or anyone else struggling to get this working. As he eloquently explained, the automatic PSR-0 handler in composer, or SPL isn't going to cut it for loading these annotations since they use Doctrine's autoloading.
So here is small complete example. Whenever you create your JMS Serializer object to begin serialization is a good time to add the annotation namespace to doctrine's autoloader. For clarity sake I'm assuming no IoC, and fully qualified namespaces (hint hint, use dependency injection):
<?php
Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
'JMS\Serializer\Annotation',
$your_app_basepath . "/vendor/jms/serializer/src");
$serializer = JMS\Serializer\SerializerBuilder::create()->build();
$json_output = $serializer->serialize('MyProject\MyClass', 'json');
Then in your MyProject\MyClass:
<?php
use JMS\Serializer\Annotation as JMS;
class MyClass{
/** #JMS\Exclude */
private $something_secret;
}
And that should cut it, autoloading the proper annotation file using doctrine instead of composer.
Check the capitalisation of your annotations. I had a similar problem when deploying from a Windows dev environment to an Ubuntu server which was caused by a typo in the case of my annotation. Windows files are case-insensitive so it works there but fails on Linux.
If you use Composer, you can get loader by specifying a path in require:
$loader = require(__DIR__ . '/../vendor/autoload.php');
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
Here is the solution
1.go to php directory then install composer
php composer-setup.php
2. go to project sdk directory
e.g.
cd /Applications/XAMPP/xamppfiles/htdocs/streetreturn/adn_sdk-php-master
update composer to install dependencies
php /Users/zakir/composer.phar update
*Note: /Users/zakir/composer.phar will be located when install composer in step 1