I've upgraded from SilverStripe 3 to 4 and now my phpUnit tests own't run because they can't find any of my Custom Classes.
There must be something missing from an autoloader or something.
I have a simple test like this
use SilverStripe\Dev\SapphireTest;
class EntityTest extends SapphireTest
{
var $Entity;
function setUp()/* The :void return type declaration that should be here would cause a BC issue */
{
parent::setUp(); // TODO: Change the autogenerated stub
$this->Entity = new \My\API\Client\Model\Entity();
}
function testMethods(){
$this->assertMethodExist($this->Entity,'setName');
}
function assertMethodExist($class, $method) {
$oReflectionClass = new ReflectionClass($class);
assertThat("method exist", true, $oReflectionClass->hasMethod($method));
}
}
and when running I get:
$ php vendor/phpunit/phpunit/phpunit mysite/tests/EntityTest.php
Fatal error: Class 'SilverStripe\Dev\SapphireTest' not found
I ran into a similar issue with SilverStripe 4.1, here is what I found (and resolved).
1) As of 4.1, you need to use --prefer-source instead of --prefer-dist to get the test code. Test code is now omitted from the distributed packages, see https://github.com/silverstripe/silverstripe-framework/issues/7845
2) phpunit must be in require-dev at version ^ 5.7 - I had a different value and this was the cause of the autoload issue.
I've created a test module for reference, see https://github.com/gordonbanderson/travistestmodule
Cheers
Gordon
You're probably missing the test bootstrapping. SS4 still relies on the SilverStripe class manifest to register available classes (not just PSR-4 autoloaders), so you need to include it. Try either of these:
$ vendor/bin/phpunit --bootstrap vendor/silverstripe/framework/tests/bootstrap.php mysite/tests
or create a phpunit.xml file in your root project:
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
</phpunit>
You may also use the equivalent file from the CMS module instead, but you probably won't see any differences until you start to integrate your testsuite into a CI provider.
Related
I am trying to add Symfony 5.4 to my legacy project. There is a pretty nice documentation on how to do this, but there's a big problem - the documentation assumes "normal" Symfony, but each time I try to install Symfony using their recommended way of composer create-project, I get a Symfony version with symfony/runtime - the big problem here, is that this version has a completely different index.php:
<?php
use App\Kernel;
require_once dirname(_DIR_).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
The documentation found here is based on a completely different index file.
I did find that I can remove the runtime package, and just copy old index, and it works for the most part, but then you also have problems with console.php and I worry that if I go this route there will be more and more problems caused by my installation expecting symfony/runtime and me manually removing it's
I tried installing Symfony 5.3 as well as different patches of 5.4, all came with this installed, even though I did work on some 5.3 / 5.4 projects and had the old school index.php file.
Does anyone know how to currently install Symfony with the "old" index.php, console.php etc.?
Thanks!
So the task is to migrate from a non-Symfony legacy app to a Symfony app. The basic idea is to allow the Symfony app to process a request and then hand it off to the legacy app if necessary. The Symfony docs show how to do this but but relies on the older style index.php file. The newer runtime based approach is a bit different.
But in the end all it really takes is a couple of fairly simple classes. A runner class takes care of creating a request object and turning it into a response. This is where you can add the bridge to your legacy app. It's a clone of Symfony's HttpKernelRunner class:
namespace App\Legacy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Runtime\RunnerInterface;
class LegacyRunner implements RunnerInterface
{
private $kernel;
private $request;
public function __construct(HttpKernelInterface $kernel, Request $request)
{
$this->kernel = $kernel;
$this->request = $request;
}
public function run(): int
{
$response = $this->kernel->handle($this->request);
// check the response to see if it should be handed off to legacy app
dd('Response Code ' . $response->getStatusCode());
$response->send();
if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($this->request, $response);
}
return 0;
}
}
Next you need to wire up runner by extending the SymfonyRuntime::getRunner method:
namespace App\Legacy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Runtime\RunnerInterface;
use Symfony\Component\Runtime\SymfonyRuntime;
class LegacyRuntime extends SymfonyRuntime
{
public function getRunner(?object $application): RunnerInterface
{
if ($application instanceof HttpKernelInterface) {
return new LegacyRunner($application, Request::createFromGlobals());
}
return parent::getRunner($application);
}
}
Finally, update composer.json to use your legacy runtime class:
"extra": {
...
"runtime": {
"class": "App\\Legacy\\LegacyRuntime"
}
}
After updating composer.json do a composer update for the changes to take effect and start your server. Navigate to a route and you should hit the dd statement.
While creating some (functional) PHPUnit tests for my Symfony 5 project, I ran into the problem, that container parameters are not properly injected into services during the tests:
For example the following parameters are available an used in the project:
// config/packages/some_bundle.yaml
some_bundle:
api:
key: 'abc' // default value is null
mode: 'live' // default value is 'sandbox'
// SomeBundle/config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
bind:
$apiMode: '%some_bundle.api.mode%'
$apiKey: '%some_bundle.api.key%'
// SomeBundle/src/Service/ApiService.php
class ApiService {
public function __construct($apiMode, $apiKey) {
// log mode and key ...
}
}
// Checking that parameters are available
$ php bin/console debug:container --parameters --env=test
Symfony Container Parameters
============================
-------------------------------------------
Parameter Value
-------------------------------------------
...
some_bundle.api.key abc
some_bundle.api.mode live
The problem:
I would assume that when using these parameters during tests, that their values are abc and live. However, logging shows, that the ApiService is created with the default values null and sandbox instead when running the tests.
The testCase extends KernelTestCase and the kernel is booted. Getting the parameter directly from the test container also shows the wrong value:
// Within the test case
$mode = self::getContainer()->getParameter('some_bundle.api.mode');
$this->assertEquals($mode, 'live', 'error');
// test output
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'live'
+'sandbox'
When using the project directly in browser, everything is fine.
The dev, prod and test environment all use the same config files, there a no special env.test files.
The phpunit.xml.dist file is setup to use the test environment, but using other environments here does not make any difference.
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ...>
<php>
<server name="APP_ENV" value="test" force="true" />
...
How to solve this? Are container parameters skipped completly when running tests and only default values can be used? Or is there anything wrong with my config?
I can't really see anything wrong with the way you have done it and would have thought it would work, although I wouldn't have injected the parameters like that. In my bundle in the DependencyInjection folder I would pass the params in the extension file instead of binding them in the services.yaml.
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yaml');
$definition = $container->getDefinition('SomeBundle/src/Service/ApiService');
$definition->addArgument($configs['api']['mode']);
$definition->addArgument($configs['api']['key']);
}
I am a newbie in Symfony but I know how to use OOP in PHP.
I try (with frustration) to couple custom parameters with Symfony configs by using Doctrine entities.
To solve the problem I used for e.g. the answer from Michael Sivolobov: https://stackoverflow.com/a/28726681/2114615 and other sources.
My solution:
Step 1: Create new package in config folder
-> config
-> packages
-> project
-> services.yaml
-> project
-> src
-> ParameterLoaderBundle.php
-> DependencyInjection
-> Compiler
-> ParameterLoaderPass.php
Step 2: Import the new resource
# config/services.yaml
...
imports:
- { resource: 'packages/project/config/services.yaml' }
...
Step 3: Package coding
# packages/project/config/services.yaml
services:
Project\:
resource: "../src"
<?php
namespace Project;
use Project\DependencyInjection\Compiler\ParameterLoaderPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ParameterLoaderBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ParameterLoaderPass(), PassConfig::TYPE_AFTER_REMOVING);
}
}
<?php
namespace Project\DependencyInjection\Compiler;
use App\Entity\SettingCategory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParameterLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$setting = $em->getRepository(SettingCategory::class)->findAll();
$container->setParameter('test', $setting);
}
}
After at all I test the new Parameter in my API controller:
$this->getParameter('Test');
But the following error message appears:
The parameter \"test\" must be defined.
Couple of things going on here. First off, loading config from a database is very unusual in Symfony so it is not surprising that you are having difficulty. Secondly, your process code is never getting called. Part of debugging is making sure that code that you expect to be called is in fact being called. Third, you really got off on a tangent with attempting to add a bundle under config. Way back in Symfony 2 there used to be more bundle related stuff under app/config and it may be that you discovered some old articles and misunderstood them.
But, the big problem here is that Symfony has what is known as a 'compile' phase which basically processes all the configuration and caches it. Hence the CompilerPassInterface. Unfortunately, services themselves are not available during the compile phase. They simply don't exist yet so no entity manager. You need to open your own database connection if you really want to load config from a database. You will want to use just a database connection object and not the entity manager since part of the compile phase is to process the entities themselves.
So get rid of all your code and just adjust your Kernel class:
# src/Kernel.php
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container)
{
$url = $_ENV['DATABASE_URL'];
$conn = DriverManager::getConnection(['url' => $url]);
$settings = $conn->executeQuery('SELECT * FROM settings')->fetchAllAssociative();
$container->setParameter('test',$settings);
}
And be aware that even if you get all this working, you will need to manually rebuild the Symfony cache after updating your settings table. It is not going to be automatic. You really might consider taking a completely different approach.
I have been trying to run FunctionalTest which extends the Symfony\Bundle\FrameworkBundle\Tests\Functional\WebTestCase with not so much success.
The issue is that:
the code in FrameworkBundle\Tests\Functional\app\AppKernel.php attempts to load autoload.php.dist in the framework bundle (not the one in the app/).
And autoload.php.dist then tries to load vendor\autoload.php which does not exist within this path.
If I remove the autoload.php.dist in the FrameworkBundle then everything is fine, but I want to avoid doing that because each time I do composer update I will then have to remove that specific fine.
I wonder what I'm doing wrong.
The exact error from the console is posted below for your information:
Configuration read from D:\xampp\htdocs\demo\app\phpunit.xml.
dist
require_once() called at [D:\xampp\htdocs\demo\vendor\sym
fony\symfony\src\Symfony\Bundle\FrameworkBundle\Tests\Functional\app\AppKernel.p
hp:26]
require_once(D:\xampp\htdocs\demo\vendor\symfony\symfony\
src\Symfony\Bundle\FrameworkBundle\Tests\Functional\app\AppKernel.php)
called at
[D:\xampp\htdocs\demo\vendor\symfony\symfony\src\Symfony\Bun
dle\FrameworkBundle\Tests\Functional\WebTestCase.php:47]
Fatal error: main(): Failed opening required
'D:\xampp\htdocs\demo\vendor\symfony\symfony/vendor/autoload.php'
(include_path='.;D:\xampp\php\PEAR') in
D:\xampp\htdocs\demo\vendor\symfony\sym
fony\autoload.php.dist on line 9
The test class simply extends WebTestCase with the setUp looks like this:
static::$kernel = static::createKernel(array('test_case'));
static::$kernel->boot();
$this->containter = static::$kernel->getContainer();
Okay. I see the problem. You are extending from Tests\Functional\WebTestCase. That is actually a test for testing the WebTestCase. You want to extend from Test\WebTestCase.
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PersonRepositoryTest extends WebTestCase
{
public function testProject()
{
$client = static::createClient();
$manager = $client->getContainer()->get('cerad_person.manager');
And you might want to get one or two working using the $client line shown above. All the setup stuff can be a bit tricky.
This one's got me stumped. I've been working with PHPUnit for a couple of months now, so I'm not that green...but I look forward to being pointed in the direction of the obvious mistake I'm making! The initialisation process outlined below works fine if I run the "app" from a browser - but PHPUnit is choking...can any one put me out of my misery?
I'm trying to test a homebrew MVC, for study purposes. It follows a typical ZF layout.
Here's the index page:
include './../library/SKL/Application.php';
$SKL_Application = new SKL_Application();
$SKL_Application->initialise('./../application/configs/config.ini');
Here's the application class (early days...)
include 'bootstrap.php';
class SKL_Application {
/**
* initialises the application
*/
public function initialise($file) {
$this->processBootstrap();
//purely to test PHPUnit is working as expected
return true;
}
/**
* iterates over bootstrap class and executes
* all methods prefixed with "_init"
*/
private function processBootstrap() {
$Bootstrap = new Bootstrap();
$bootstrap_methods = get_class_methods($Bootstrap);
foreach ($bootstrap_methods as $method) {
if(substr($method,0,5) == '_init'){
$bootstrap->$method();
}
}
return true;
}
}
Here's the test:
require_once dirname(__FILE__).'/../../../public/bootstrap.php';
require_once dirname(__FILE__).'/../../../library/SKL/Application.php';
class SKL_ApplicationTest extends PHPUnit_Framework_TestCase {
protected $object;
protected function setUp() {
$this->object = new SKL_Application();
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown() {
}
public function testInitialise() {
$this->assertType('boolean',$this->object->initialise());
}
}
But I keep stumbling at the first hurdle!!
PHP Warning: include(bootstrap.php): failed to open stream:
No such file or directory in path\to\files\SKL\Application.php on line 9
any ideas?
Use include_once or better yet require_once instead of include to include the bootstrap.php in the Application class file. Despite being already loaded include loads it again but since it's obviously not on the include path you get the error.
Thanks to Raoul Duke for giving me a push in the right direction, here's where I got to so far
1 - add the root of the application to the include path
2 - make all inclusion paths relative to the root of the application
3 - include a file in your unit tests that performs the same function, but compensates for the relative location when it is included. I just used realpath() on the directory location of the files.
The problem I have now is that the darn thing won't see any additional files I'm trying to pass it.
So, I'm trying to test a configuration class, that will parse a variety of filetypes dynamically. The directory structure is like this:
Application_ConfigTest.php
config.ini
The first test:
public function testParseFile() {
$this->assertType('array',$this->object->parseFile('config.ini'));
}
The error:
failed to open stream: No such file or directory
WTF? It's IN the same directory as the test class...
I solved this by providing an absolute (i.e. file structure) path to the configuration file.Can anyone explain to me how PHPUnit resolves it's paths, or is it because the test class itself is included elsewhere, rendering relative paths meaningless?