Why is my custom test not being launched by PHPUnit? - phpunit

As described in the PHPUnit doc, you can implement PHPUnit\Framework\Test to write custom tests.
Sound great, but how can I launch those tests as part of my testsuite?
Given the following test directory:
+tests/
|- NormalTestExtendingTestCaseTest.php
|- CustomTestImplementingTest.php
phpunit.xml
And my phpunit.xml file:
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite name="My Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>
Only NormalTestExtendingTestCaseTest.php is executed by PHPUnit.
CustomTestImplementingTest.php
<?php
use PHPUnit\Framework\TestCase;
class CustomTestImplementingTest implements PHPUnit\Framework\Test
{
public function count()
{
return 1;
}
public function run(PHPUnit\Framework\TestResult $result = null)
{
if ($result === null) {
$result = new PHPUnit\Framework\TestResult;
}
$result->startTest($this);
// do something
$result->endTest($this, $stopTime);
return $result;
}
}

Check that your classes has the correct inheritance, the correct name methods (starting with 'test'), and the correct namespace.
You maybe have to run the composer dump-autoload.

I found a solution for this problem:
The custom test class should implement a suite() method so PHPUnit run the resulting TestSuite (with our test(s))
The test class should declare a getter method getName() (even if it's not declared in the interface PHPUnit\Framework\Test or an exception is thrown at execution. Moreover, the name should not be null or an other exception is throw at execution.
So the final class is
<?php
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use SebastianBergmann\Timer\Timer;
use PHPUnit\Framework\Test;
class CustomTestImplementingTest implements Test {
protected $name = '';
/**
* This method create a TestSuite for this Test
*
* #return TestSuite
* #throws ReflectionException
*/
public static function suite() {
$classname = (new \ReflectionClass(static::class))->getShortName();
$suite = new TestSuite();
$suite->setName($classname);
$suite->addTest(new static());
return $suite;
}
/**
* #return mixed
*/
public function getName() {
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name): void {
$this->name = $name;
}
public function count() {
return 1;
}
public function run(TestResult $result = null): TestResult {
if ($result === null) {
$result = new PHPUnit\Framework\TestResult;
}
Timer::start();
$stopTime = null;
$result->startTest($this);
try {
// do something
} catch (PHPUnit\Framework\AssertionFailedError $e) {
$result->addFailure($this, $e, $stopTime);
} catch (Exception $e) {
$result->addError($this, $e, $stopTime);
} finally {
$stopTime = Timer::stop();
}
$result->endTest($this, $stopTime);
return $result;
}
}

Related

How to toggle Symfony's php-translation/symfony-bundle EditInPlace

I followed this documentation for Edit In Place, and setup the Activator, and it works!
However, I will be using this on the production site and allowing access via a ROLE_TRANSLATOR Authorization. This is also working, but I don't want the web interface always "on"
How would I go about enabling it via some sort of link or toggle?
My thoughts, it would be simple to just add a URL parameter, like ?trans=yes and then in the activator;
return ($this->authorizationChecker->isGranted(['ROLE_TRANSLATOR']) && $_GET['trans'] == 'yes');
Obviously, $_GET would not work, I didn't even try.
How do I generate a link to simply reload THIS page with the extra URL parameter
How do I check for that parameter within the "Activator"
or, is there a better way?
The "proper" way to do this, as I have discovered more about "services" is to do the logic diectly in the RoleActivator.php file.
referencing documentation for How to Inject Variables into all Templates via Referencing Services I came up with the following solution;
src/Security/RoleActivator.php
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Translation\Bundle\EditInPlace\ActivatorInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class RoleActivator implements ActivatorInterface
{
/**
* #var AuthorizationCheckerInterface
*/
private $authorizationChecker;
/**
* #var TranslatorInterface
*/
private $translate;
/**
* #var RequestStack
*/
private $request;
private $params;
private $path;
private $flag = null;
public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translate, RequestStack $request)
{
$this->authorizationChecker = $authorizationChecker;
$this->translate = $translate;
$this->request = $request;
}
/**
* {#inheritdoc}
*/
public function checkRequest(Request $request = null)
{
if ($this->flag === null) { $this->setFlag($request); }
try {
return ($this->authorizationChecker->isGranted(['ROLE_TRANSLATOR']) && $this->flag);
} catch (AuthenticationCredentialsNotFoundException $e) {
return false;
}
}
public function getText()
{
if ($this->flag === null) { $this->setFlag(); }
return ($this->flag) ? 'linkText.translate.finished' : 'linkText.translate.start'; // Translation key's returned
}
public function getHref()
{
if ($this->flag === null) { $this->setFlag(); }
$params = $this->params;
if ($this->flag) {
unset($params['trans']);
} else {
$params['trans'] = 'do';
}
$queryString = '';
if (!empty($params)) {
$queryString = '?';
foreach ($params as $key => $value) {
$queryString.= $key.'='.$value.'&';
}
$queryString = rtrim($queryString, '&');
}
return $this->path.$queryString;
}
private function setFlag(Request $request = null)
{
if ($request === null) {
$request = $this->request->getCurrentRequest();
}
$this->flag = $request->query->has('trans');
$this->params = $request->query->all();
$this->path = $request->getPathInfo();
}
}
config\packages\twig.yaml
twig:
# ...
globals:
EditInPlace: '#EditInPlace_RoleActivator'
config\services.yaml
services:
# ...
EditInPlace_RoleActivator:
class: App\Security\RoleActivator
arguments: ["#security.authorization_checker"]
So What I added over and above the php-translation example is the getText and getHref methods and corresponding private variables being set in the checkRequest and read there after.
Now in my twig template (in the header) I just use
{% if is_granted('ROLE_TRANSLATOR') %}
{{ EditInPlace.Text }}
{% endif %}
Add the new keys to the translation files and your done. the trans=do query parameter is toggled on and off with each click of the link. You could even add toggling styles with a class name, just copy the getText method to something like getClass and return string a or b with the Ternary.

PHPUnit and Jenkins: Datetime expected and Datetime actual

I have the test class:
class ClassTest extends \PHPUnit_Framework_TestCase
{
/**
* #covers \Path\Class::method()
* #dataProvider methodData
*/
public function testMethod($data, $expected)
{
$this->object = $this->getMockBuilder("Path\Class")
->setConstructorArgs([..])
->getMock();
....
$response = $this->object->method($data);
$this->assertEquals($expected, $response);
}
public function methodData()
{
$entity= new Entity();
$entity->setDateArrivee(new \DateTime());
....
....
}
}
I run the PHPunit in Jenkins job. the datetime of dateArrivee in the response and the expected is not same. I don't know why.
Can you help me ?
I write this method in the testMethod
$entity->setDateArrivee(new \DateTime());

PhpUnit dataProviders vs Coverage

I decided, that it will be fine if I use data providers but when i try to generate code coverage whole tested class has 0% coverage.. Can someone tell me why?
Test class:
class AuthorDbManagerTest extends AbstractDbManagerTest
{
public function setUp()
{
parent::setUp();
}
/**
* #dataProvider instanceOfProvider
* #param bool $isInstanceOf
*/
public function testInstances(bool $isInstanceOf)
{
$this->assertTrue($isInstanceOf);
}
public function instanceOfProvider()
{
$manager = new AuthorDbManager($this->getEntityManagerMock());
return [
"create()" => [$manager->create() instanceof Author],
"save()" => [$manager->save(new Author()) instanceof AuthorDbManager],
"getRepository" => [$manager->getRepository() instanceof EntityRepository],
];
}
}
Tested class:
class AuthorDbManager implements ManagerInterface
{
protected $entityManager;
protected $repository;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository(Author::class);
}
public function create(array $data = [])
{
return new Author();
}
public function getRepository(): EntityRepository
{
return $this->repository;
}
public function save($object): ManagerInterface
{
$this->entityManager->persist($object);
$this->entityManager->flush();
return $this;
}
}
Why my code coverage is 0% on AuthorDbManager?
Screen
The data in the DataProvider is collected before the actual tests start - and there is nothing useful being tested within the testInstances() method.
If you passed the classname and expected class into testInstances($methodName, $expectedClass):
public function testInstances(callable $method, $expectedClass)
{
$this->assertInstanceOf($expectedClass, $method());
}
The dataprovider could return a callable, and the expected result:
"create()" => [[$manager,'create'], Author::class],
then you'd at least be running the code with in the actual test. You may also be better to just pass back a string methodname - 'create', and run that with a locally created $manager instance - $manager->$method() in the test.
In general, it's best to test something as specific as you can - not just letting it convert to a true/false condition.

Testion controler method in symfony (phpUnit)

i need some help i want to write a unit test about a controler method , i have searched for examples and tested a lot of method's but none of them has worked:
Here is my controller:
class ComputerController extends Controller
{
/**
* #Route("/list-computers.html", name="back_computer_list")
* #return RedirectResponse|Response
*/
function listComputerAction()
{
$ad = $this->get("ldap_service");
$computers = $ad->getAllComputer();
return $this->render('BackBundle:Computer:list.html.twig', array(
"computers" => $computers,
));
}
I have tried to test it with mock like this:
class ComputerController extends Controller
{
/**
* #var EngineInterface
*/
private $templating;
public function setTemplating($templating)
{
$this->templating = $templating;
}
and i have created a test method:
class ComputerControllerTest extends TestCase {
public function testlistComputerAction(){
$templating = $this->getMockBuilder('BackBundle\Controller\ComputerController')->getMock();
$computers = [1,2];
$templating->expects($this->once())
->method('render')
->with('BackBundle:Computer:list.html.twig', array(
"computers" => $computers))
->will($this->returnValue( $computers));
$controller = new ComputerController();
$controller->setTemplating($templating);
$this->assertEquals('success', $controller->listComputerAction());
}
When i start executing phpunit , i have this warning"Trying to configure method "render" which cannot be configured because it does not exist, has not been specified, is final, or is static"
I would be thankful if someone has an idea about this
I tried to Test a method in ldapService : Here is the method's of the service that i want to test
/**
* #return bool|resource
*/
public function getLdapBind()
{
if (!$this->ldapBind) {
if ($this->getLdapConnect()) {
$this->ldapBind = #ldap_bind($this->ldapConnect, $this->ldapUser, $this->ldapPass);
}
}
return $this->ldapBind;
}
/**
* #param $ldapUser
* #param $password
* #return bool
*/
function isAuthorized($ldapUser, $password)
{
$result = false;
if ($this->ldapConnect) {
$result = #ldap_bind($this->ldapConnect, $ldapUser, $password);
}
return $result;
}
Here is the test (using Mock):
<?php
namespace BackBundle\Tests\Service;
use PHPUnit\Framework\TestCase;
use BackBundle\Service\LdapService;
use PHPUnit_Framework_MockObject_InvocationMocker;
class LdapServiceTest extends TestCase {
public function testgetLdapConnect()
{
// $LdapService = new LdapService();
$ldapMock = $this->getMockBuilder( 'LdapService')->setMethods(['getLdapBind'])->disableOriginalConstructor()->getMock();
$ldapMock->expects($this->once())
// ->method()
->with(array('ldap_bind', 'mike', 'password'))
->will($this->returnValue(true));
$ldapMock->isAuthorized('mike', 'password');
}
}
But i have a warning that i can't resolve : "Method name matcher is not defined, cannot define parameter matcher without one"
If someone , has an idea about that please
Honestly, there is nothing useful to test in that three-line controller. #1 is the service container, and #3 is the Twig subsystem. Line #2 can be unit tested on it's own.
With more complex controllers, I have found that making them a service where all the dependencies are passed in, either by constructor, or into the action itself does make slightly more complex controllers quite easy, but very few need that anyway.

How to use a csv file in PHPUnit test

I wrote a DataTest case following the example 4.5 of PHPUnit manual, the url is: http://www.phpunit.de/manual/3.6/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers. But I came across with an error:
The data provider specified for DataTest::testAdd is invalid.
Data set #0 is invalid.
I thought it maybe that I edit the data.csv file in a wrong way, then I used php function fputcsv() to create data.csv file, but it also didn't work, I want to know why, and how to resolve this problem. Thanks!
P. S.: the data in data.csv is:
0,0,0
0,1,1
The codes are show as follows:
DataTest.php
require 'CsvFileIterator.php';
class DataTest extends PHPUnit_Framework_TestCase
{
public function provider()
{
return new CsvFileIterator('data.csv');
}
/**
* #dataProvider provider
*/
public function testAdd($a, $b, $c)
{
$this->assertEquals($c, $a + $b);
}
}
CsvFileIterator.php
class CsvFileIterator implements Iterator
{
protected $file;
protected $key = 0;
protected $current;
public function __construct($file)
{
$this->file = fopen($file, 'r');
}
public function __destruct()
{
fclose($this->file);
}
public function rewind()
{
rewind($this->file);
$this->current = fgetcsv($this->file);
$this->key = 0;
}
public function valid()
{
return !feof($this->file);
}
public function key()
{
return $this->key;
}
public function current()
{
return $this->current;
}
public function next()
{
$this->current = fgetcsv($this->file);
$this->key++;
}
}
The data.csv file is create by function fputcsv():
$data = array(
array(0, 0, 0),
array(0, 1, 1)
);
$fp = fopen('data.csv', 'w');
foreach($data as $v)
{
fputcsv($fp, $v);
}
fclose($fp);
Example :-)
/**
* #dataProvider provider
* #group csv
*/
public function testAdd($a, $b, $c)
{
$this->assertEquals($c, $a + $b);
}
/**
* #return array
*/
public function provider()
{
$file = file_get_contents("/Volumes/htdocs/contacts.csv","r");
foreach ( explode("\n", $file, -1) as $line )
{
$data[] = explode(',', $line);
}
return $data;
}
/*
* CREATE TO CSV FILE DATAPROVIDER
* don't create this file in your test case
*/
public function saveToCsv()
{
$list = array(
array(0,0,0),
array(0,1,1)
);
$file = fopen("/Volumes/htdocs/contacts.csv","w");
foreach ($list as $line)
{
fputcsv($file,$line);
}
fclose($file);
}
#ZongshuLin Similar issue here. Possible Solutions:
Check this data.csv. Basically I had to add a row after the last line.
You can also check my approach on GitHub when specifying the data.csv location
Use DIRECTORY_SEPARATOR constant so the script may run on any OS--Windows uses backslashes while Linux slashes.
/**
* #return CsvFileIterator
*/
public function additionProvider()
{
return new CsvFileIterator(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage/data.csv');
}

Resources