Passing data provider to setUp() in PHPUnit - phpunit

I'm currently trying to pass data from my data provider to the setUp()-method in PHPUnit.
Background: I am using PHPUnit for running frontend-tests in different browsers. The browser should be defined inside the data provider and needs to be known by the setUp()-method.
I understand, that a data provider initially is executed before the setUp()-method (as setUpBeforeClass()) is called. Therefore setUp()-data can not be passed to a data provider. But it should work the other way round, shouldn't it?
Does PHPUnit generate its own temporarily testclasses with data from the data provider "integrated"?
Of course: a workaround could be, to read the XML-file in the setUp()-method again. But that's the last option, I'd consider...
EDIT: Provided a small snippet:
part of dataProvider():
public function dataProvider()
{
$this->xmlCnf = $data['config'];
var_dump($this->xmlCnf); // array with config is exposed
// [...]
}
And the setUp()-method:
protected function setUp()
{
var_dump($this->xmlCnf); // NULL
//[...]
}

In case this is useful to anyone:
The following code should work:
public function dataProvider()
{
return [ [ /* dataset 1 */] , ... ]
}
protected setUp() {
parent::setUp();
$arguments = $this->getProvidedData();
// $arguments should match the provided arguments for this test case
}
/**
* #dataProvider dataProvider
*/
public function testCase(...$arguments) {
}
The getProvidedData method seems to have been available since PHPUnit 5.6 (which was either shortly before or after this question was originally asked)

we can make the xmlCnf to static
private static $xmlCnf;
public function provider(){
self::$xmlCnf = 'hello';
var_dump(self::$xmlCnf); //hello
return [...];
}
public function setUp() {
var_dump(self::$xmlCnf); //hello
parent::setUp();
}

Related

Symfony - configure class from `service.yaml` with static default value

I am trying to create a Class that can be call from anywhere in the code.
It accepts different parameters that can be configured from the constructor (or setters).
This Class will be shared between several projects, so I need to be able to easily configure it once and use the same configuration (or different/specific one) multiple times.
Here's my class:
namespace Allsoftware\SymfonyBundle\Utils;
class GdImageConverter
{
public function __construct(
?int $width = null,
?int $height = null,
int|array|null $dpi = null,
int $quality = 100,
string $resizeMode = 'contain',
) {
$this->width = $width ? \max(1, $width) : null;
$this->height = $height ? \max(1, $height) : null;
$this->dpi = $dpi ? \is_int($dpi) ? [\max(1, $dpi), \max(1, $dpi)] : $dpi : null;
$this->quality = \max(-1, \min(100, $quality));
$this->resizeMode = $resizeMode;
}
}
Most of the time, the constructor parameters will be the same for ONE application.
So I thought of using a private static variable that corresponds to itself, but already configured.
So I added the $default variable:
namespace Allsoftware\SymfonyBundle\Utils;
class GdImageConverter
{
private static GdImageConverter $default;
public function __construct(
?int $width = null,
?int $height = null,
int|array|null $dpi = null,
int $quality = 100,
string $resizeMode = 'contain',
) {
// ...
}
public static function setDefault(self $default): void
{
self::$default = $default;
}
public static function getDefault(): self
{
return self::$default ?? self::$default = new self();
}
}
Looks like a Singleton but not really.
To set it up once and use GdImageConverter::getDefault() to get it, I wrote these lines inside the service.yaml file:
services:
default.gd_image_converter:
class: Allsoftware\SymfonyBundle\Utils\GdImageConverter
arguments:
$width: 2000
$height: 2000
$dpi: 72
$quality: 80
$resizeMode: contain
Allsoftware\SymfonyBundle\Utils\GdImageConverter:
calls:
- setDefault: [ '#default.gd_image_converter' ]
ATE when calling GdImageConverter::getDefault(), it does not correspond to the default.gd_image_converter service.
$default = GdImageConverter::getDefault();
$imageConverter = new GdImageConverter(2000, 2000, 72, 80);
dump($default);
dump($imageConverter);
die();
And when debugging self::$default inside getDefault(), it's empty.
What am I doing wrong ?
Note: When I change the calls method setDefault to a non-existing method setDefaults, symfony tells me that the method is not defined.
Invalid service "Allsoftware\SymfonyBundle\Utils\GdImageConverter": method "setDefaults()" does not exist.
Thank you!
Decided to post a new and hopefully more coherent answer.
The basic problem is that GdImageConverter::getDefault(); returns an instance for which all the arguments are null. And that is because the Symfony container only creates services when they are asked for (aka injected). setDefault is never called so new self() is used.
There is a Symfony class called MimeTypes which employs a similar pattern but it does not try to customize the service so it does not matter.
There is a second problem with the way the GdImageConverter service is configured. It will basically inject a 'null' version even though it does set the default instant correctly.
To fix the second problem you need to call setDefault with the current service and just get rid of default.gd_image_converter unless you need it for something else:
services:
App\Service\GdImageConverter:
class: App\Service\GdImageConverter
public: true
arguments:
$width: 2000
$height: 2000
$dpi: 72
$quality: 80
$resizeMode: contain
calls:
- setDefault: [ '#App\Service\GdImageConverter' ]
As a side note, the static method setDefault will be called dynamically. This is a bit unusual but it is legal in PHP and Symfony does it for other classes.
Next we need to ensure the service is always instantiated. This is a rare requirement and I don't think there is a default way to do so. But using Kernel::boot works:
# src/Kernel.php
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function boot()
{
parent::boot();
$this->container->get(GdImageConverter::class);
}
}
This ensures that the default service is set for both commands and web applications. GdImageConverter::getDefault(); can now be called at anytime and will return the initialized service. Notice that the service had to be declared public for Container::get to work.
You could stop here but always creating a service even though you probably don't usually need it is kind of annoying. It is possible to avoid doing that by injecting the container itself into your class.
This definitely violates Symfony's recommended practices and if the reader feels they need to downvote the answer for even suggesting it then do what you need to do. However the Laravel framework uses this approach (called facades) on a routine basis and those apps somehow manage to work.
use Psr\Container\ContainerInterface;
class GdImageConverter
{
private static GdImageConverter $default;
private static ContainerInterface $container; // Add this
public static function setContainer(ContainerInterface $container)
{
self::$container = $container;
}
public static function getDefault(): self
{
//return self::$default ?? self::$default = new self();
return self::$default ?? self::$default = self::$container->get(GdImageConverter::class);
}
}
# Kernel.php
public function boot()
{
parent::boot();
GdImageConverter::setContainer($this->container);
}
And now we are back to lazy instantiation.
And while I won't provide the details you could eliminate the need to inject the container as well as making the service public by injecting a GdImageConverterServiceLocater.

how to use Symfony methods Action excluding the "Action" word

I am currently migrating an existent application to Symfony2 that has about 100 controllers with approximately 8 actions in each controller. All the current Actions are named as follow:
public function index(){}
However the default naming convention for Symfony is indexAction().
Is it possible to keep all my current actions and tell Symfony to use as it is without the "Action" word after the method name?
thank you.
Yes, this is possible. You should be able to define routes as normal, but you need to change the way the kernel finds the controller. The best way to do this is to replace/decorate/extends the service 'controller_name_converter'. This is a private service and is injected into the 'controller_resolver' service.
The source code of the class you want to replace is at 'Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser'.
Basically, the code runs like this. The 'bundle:controller:action' you specified when creating the route is saved in the cache. When a route is matched, that string is given back to the kernel, which in turn calls 'controller_resolver' which calls 'controller_name_resolver'. This class convert the string into a "namespace::method" notation.
Take a look at decorating services to get an idea of how to do it.
Here is an untested class you can work with
class ActionlessNameParser
{
protected $parser;
public function __construct(ControllerNameParser $parser)
{
$this->parser = $parser;
}
public function parse($controller)
{
if (3 === count($parts = explode(':', $controller))) {
list($bundle, $controller, $action) = $parts;
$controller = str_replace('/', '\\', $controller);
try {
// this throws an exception if there is no such bundle
$allBundles = $this->kernel->getBundle($bundle, false);
} catch (\InvalidArgumentException $e) {
return $this->parser->parse($controller);
}
foreach ($allBundles as $b) {
$try = $b->getNamespace().'\\Controller\\'.$controller.'Controller';
if (class_exists($try)) {
// You can also try testing if the action method exists.
return $try.'::'.$action;
}
}
}
return $this->parser->parse($controller);
}
public function build($controller)
{
return $this->parser->build($controller);
}
}
And replace the original service like:
actionless_name_parser:
public: false
class: My\Namespace\ActionlessNameParser
decorates: controller_name_converter
arguments: ["#actionless_name_parser.inner"]
Apparently the Action suffix is here to distinguish between internal methods and methods that are mapped to routes. (According to this question).
The best way to know for sure is to try.
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class HelloController
{
/**
* #Route("/hello/{name}", name="hello")
*/
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
Try to remove the Action from the method name and see what happens.

In phpunit what is the difference between __construct versus setup?

I am curious to know it is good practice to create object in test class __construct or we should always use setup/teardown approach ( or setUpBeforeClass/tearDownAfterClass approach)?
I aware of the fact set/teardown gets called for each test so will it do any good if I put my object creation code in it? e.g.
//mytestclass.php
class MyTestClass extends PHPUnit_Framework_TestCase
{
private $obj;
protected function setUp()
{
$this->obj = new FooClass();
}
public testFooObj()
{
//assertions for $this->obj
}
...
}
what could be the issues if I create object in constructor like this:
class MyTestClass extends PHPUnit_Framework_TestCase
{
private $obj;
protected function __construct()
{
$this->obj = new FooClass();
}
public testFooObj()
{
//assertions for $this->obj
}
...
}
I tried googling around as well as PHPUnit documentation couldn't get much information about, Can you please help me to understand which one is good practice?
setUp() gets called before each of your tests is ran. __construct() happens when your class is instantiated. So if you have multiple tests and they use local properties and modify them, using setUp() you can ensure that they are the same before each test is ran. The opposite of setUp() is tearDown() where you can ensure that test data gets cleaned up after each test.
As I have just found out, implementing the default class constructor instead of the setupBeforeClass() method breaks the #dataProvider annotations (probably all kinds of annotations), yielding a "Missing argument" exception for any parameterized tests.
Missing argument 1 for AppBundle\Tests\Service\InvitationVerifierTest::testDireccionInvalida()
Replacing public function __construct() for public static function setUpBeforeClass() gets rid of the exception. So there it goes, favor the setupBeforeClass() method over the regular constructor.
PHPUnit version 4.5.0

symfony test database insert

I have a functional test that creates and persists some things in the database and I want to test that the correct number of items was inserted (there is a scenario where it currently inserts two instead of one).
In the controller everything seems to work and if I use the code below (in the controller) to debug it, I get the expected (wrong) value of "2":
$em = $this->getDoctrine()->getManager();
$fooRepo = $em->getRepository('CompanyProjectBundle:Foo');
$foos = $fooRepo->retrieveByBar(3);
echo count($foos); // Gives a result of 2
However, if I try something similar from within my Test class I get zero...
/**
* {#inheritDoc}
*/
protected function setUp()
{
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager()
;
$this->em->getConnection()->beginTransaction();
}
/**
* {#inheritDoc}
*/
protected function tearDown()
{
parent::tearDown();
$this->em->getConnection()->rollback();
$this->em->close();
}
public function testFooForm()
{
// ... do some testing
$fooRepo = $this->em->getRepository('CompanyProjectBundle:Foo');
$foos = $fooRepo->retrieveByBar(3);
echo count($foos); // gives a result of ZERO
// ... more happens later
}
Is it getting a different entity manager or something like that? Should I be using some other method to get hold of the correct EM so I can then view the same data that the app is running from?
Everything's running inside a transaction (which is rolled back when the test client is destroyed), but that happens after the snippet shown above.
Ah... solved my own problem. I think I was getting the wrong EntityManager. I fixed it by getting the EntityManager via the client's container instead of the kernel's one:
public function testFooForm()
{
// ... do some testing
$clientEm = $client->getContainer()->get('doctrine.orm.entity_manager');
$fooRepo = $clientEm->getRepository('CompanyProjectBundle:Foo');
$foos = $fooRepo->retrieveByBar(3);
echo count($foos); // gives the correct result of 2
// ... more happens later
}

flexunit: Parametrized tests

I am trying to run a parametrized tests... Was trying to implement it like it explained here:
http://docs.flexunit.org/index.php?title=Parameterized_Test_Styles
Here is what my test case looking
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
[Paremeters]
public static var stackProvider:Array = [new ArrayBasedStack(), new LinkedListBasedStack()] ;
private var _stack:IStack;
public function ArrayBasedStackTests(param:IStack)
{
_stack = param;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stackProvider" )]
public function isEmptyStackPositiveTest():void
{
var stack:IStack = _stack;
assertEquals( true, stack.isEmpty() );
}
But this code throws following initializing Error:
Error: Custom runner class org.flexunit.runners.Parameterized should
be linked into project and implement IRunner. Further it needs to have
a constructor which either just accepts the class, or the class and a
builder.
Need help to fix it
UPDATE
I've updated the code so it looks like this
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
private var foo:Parameterized;
[Parameters]
public static function stacks():Array
{
return [ [new ArrayBasedStack()], [new LinkedListBasedStack()] ] ;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stacks")]
public function isEmptyStackPositiveTest(stack:IStack):void
{
assertEquals( true, _stack.isEmpty() );
}
It works. But the result is a bit strange. I have 4 test executed instead of 2. (I have 2 items in data provider, so cant get why do I have 4 tests).
Output
http://screencast.com/t/G8DHbcjDUkJ
The [Parameters] meta-data specifies that the parameters are passed to the constructor of the test - so the test class is called for each parameter. You also have the dataProvider set for the specific test method, so the test method is also called once for each parameter. Two calls for the test, and two calls to the method, ends up running four tests.
The solution is to either use [Parameters] meta-tag which specifies the data to use for the whole test class, or use the dataProvider for each test method, but not both with the same data at the same time.
You're missing the static reference to Paramaterized, as shown here:
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class MyTestNGTest
{
private var foo:Parameterized;
...
Basically, that error means that the [Runner] defined isn't available at runtime, which occurs if there is no static reference in the class to cause it to get linked in.
In FlexUnit 4.5.1, this approach changed to using [Rule]'s like so:
public class MyTestNGTest
{
[Rule]
public function paramaterizedRule:ParamaterizedRule = new ParamaterizedRule();
...
}
However, I can't seem to see an actual implementation of IMethodRule for paramaterized tests (that example is fictional).

Resources