When I run phpunit --testdox with #dataProvider I get output like this:
MyTestClass
✔ My function data set ""
✔ My function data set ""
✔ My function data set ""
Is there a way of getting testdox to display a friendly description for each data set? Something like:
MyTestClass
✔ My function data set "one"
✔ My function data set "two"
✔ My function data set "three"
Example code:
class MyTestClassTest extends \PHPUnit\Framework\TestCase
{
/**
* #dataProvider dataSets
*/
public function testMyFunction(string $data)
{
$this->assertTrue(true);
}
public function dataSets()
{
return [
['one'],
['two'],
['three'],
];
}
}
EDIT
I'm using phpunit version 7.3.2
Add index to your date sets
class MyTestClassTest extends \PHPUnit\Framework\TestCase
{
/**
* #dataProvider dataSets
*/
public function testMyFunction(string $data)
{
$this->assertTrue(true);
}
public function dataSets()
{
return [
'one' => ['one'],
'two' => ['two'],
'three' => ['three'],
];
}
}
This will output:
My Test Class
✔ My function with one
✔ My function with two
✔ My function with three
Or, you may add #testdox to your docblock
class MyTestClassTest extends \PHPUnit\Framework\TestCase
{
/**
* #dataProvider dataSets
* #testdox My custom testdox output with $data
*/
public function testMyFunction(string $data)
{
$this->assertTrue(true);
}
public function dataSets()
{
return [
['one'],
['two'],
['three'],
];
}
}
This will output:
My Test Class
✔ My custom testdox output with one
✔ My custom testdox output with two
✔ My custom testdox output with three
Related
I want to write a RestResource based on ResourceBase. Most of this is working ok, but I wanted to inject a service class to house db/repo logic; in order to do this, I attempted to overload the create // construct methods of ResourceBase, but when I apply the code from core/modules/rest/src/Plugin/ResourceBase.php :
public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->serializerFormats = $serializer_formats;
$this->logger = $logger;}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->getParameter('serializer.formats'),
$container->get('logger.factory')->get('rest')
);
}
and then run cache-rebuild, with my module enabled, the logger.factory->rest seems to be returning a LoggerChannel instead of a LoggerInterface:
Uncaught TypeError: Argument 5 passed to Drupal\xxxxx\Plugin\rest\resource\xxxxx::__construct() must be an instance of Drupal\xxxxx\Plugin\rest\resource\LoggerInterface, instance of Drupal\Core\Logger\LoggerChannel given
what is the correct container / service reference for what is required here?
You are probably declaring LoggerInterface's alias wrong.
Replace:
use Drupal\xxxxx\Plugin\rest\resource\LoggerInterface
with:
use Psr\Log\LoggerInterface;
Good point. I re-examined what I had, and added the above using statement. Also checked my services.yml to make sure I used the correct service resolution for the DI (xxx.repository), and had to cross-check all the namespaces
final version (with repo injection below):
namespace Drupal\xxx\Plugin\rest\resource;
use Drupal\xxx;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Datetime\DrupalDateTime;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Psr\Log\LoggerInterface;
/**
* Provides Resource
*
* #RestResource(
* id = "xxx_resource",
* label = #Translation("XXX Resource"),
* serialization_class = "",
* uri_paths = {
* "canonical" = "/xxx",
* "create" = "/xxx/callback"
* }
* )
*/
class XXXResource extends ResourceBase {
private \Drupal\XXX\Repository $repository;
public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, \Drupal\XXX\Repository $repo) {
parent::__construct($configuration, $plugin_id, $plugin_definition,$serializer_formats,$logger,$repo);
$this->serializerFormats = $serializer_formats;
$this->logger = $logger;
$this->repository=$repo;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->getParameter('serializer.formats'),
$container->get('logger.factory')->get('rest'),
$container->get('xxx.repository')
);
}
//add post/get / etc behaviors below, then enable via RestUI module
Images aren't saving with settings below
public function configureFields(string $pageName): iterable
{
return [
ImageField::new('imageFile')->setBasePath('%app.path.product_images%'),
];
}
This working for me...
First create VichImageField
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Vich\UploaderBundle\Form\Type\VichImageType;
class VichImageField implements FieldInterface
{
use FieldTrait;
public static function new(string $propertyName, ?string $label = null)
{
return (new self())
->setProperty($propertyName)
->setTemplatePath('')
->setLabel($label)
->setFormType(VichImageType::class);
}
}
And
public function configureFields(string $pageName): iterable
{
return [
ImageField::new('imagename')->setBasePath($this->getParameter('app.path.product_images'))->onlyOnIndex(),
VichImageField::new('imageFile')->hideOnIndex()
];
}
More info here
https://symfony.com/doc/master/bundles/EasyAdminBundle/fields.html#creating-custom-fields
Make sure to change at least 1 doctrine mapped field in your setter, otherwise doctrine won't dispatch events. Here is an example from the docs:
/**
* #ORM\Column(type="datetime")
* #var \DateTime
*/
private $updatedAt;
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
// VERY IMPORTANT:
// It is required that at least one field changes if you are using Doctrine,
// otherwise the event listeners won't be called and the file is lost
if ($image) {
// if 'updatedAt' is not defined in your entity, use another property
$this->updatedAt = new \DateTime('now');
}
}
You need the resolve the parameter first.
Instead of
ImageField::new('imageFile')->setBasePath('%app.path.product_images%')
Try
...
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class ProductCrudController extends AbstractCrudController
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function configureFields(string $pageName): iterable
{
return [
ImageField::new('imageFile')>setBasePath($this->params->get('app.path.product_images'))
];
}
More info on getting the parameter here:
https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service
According to the documentation
If the value passed to the date filter is null, it will return the
current date by default. If an empty string is desired instead of the
current date, use a ternary operator:
http://twig.sensiolabs.org/doc/2.x/filters/date.html
The problem is the solution provided entails that we revisit all dates in the application and apply the ternary operation as we never want to show today's date instead of null.
is it possible to override the default date filter? if so how can I implement this. We're using twigs with symfony 2.7
As explained here in the doc, you can override an existing filter:
To overload an already defined filter, test, operator, global
variable, or function, re-define it in an extension and register it as
late as possible (order matters).
Here is the code to return an empty string instead of the current date if null:
class DateEmptyIfNull extends Twig_Extension
{
public function getFilters()
{
return array(
new Twig_Filter('date', array($this, 'dateFilter')),
);
}
public function dateFilter($timestamp, $format = 'F j, Y H:i')
{
$result = '';
if($timestamp !== null)
{
$result = parent::dateFilter($timestamp, $format);
}
return $result;
}
}
$twig = new Twig_Environment($loader);
$twig->addExtension(new DateEmptyIfNull());
From the documentation:
If the value passed to the date filter is null, it will return the current date by default. If an empty string is desired instead of the current date, use a ternary operator:
{{ post.published_at is empty ? "" : post.published_at|date("m/d/Y") }}
You can check it at https://twig.symfony.com/doc/3.x/filters/date.html
Here's the Twig 3.0 solution
Extension class:
namespace Application\Twig\Extensions;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class DateWithFallback extends AbstractExtension
{
/**
* #var Environment
*/
protected $twig;
/**
* DateWithFallback constructor.
*
* #param Environment $twig
*/
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
/**
* #return array|TwigFilter[]
*/
public function getFilters(): array
{
return [
new TwigFilter('date', [$this, 'dateFilter']),
];
}
/**
* #param string|null $timestamp
* #param string $fallback
* #param string $format
* #return string
*/
public function dateFilter(?string $timestamp, string $fallback = 'Not set', string $format = 'd/m/Y'): string
{
if ($timestamp !== null) {
return twig_date_format_filter($this->twig, $timestamp, $format);
}
return $fallback;
}
}
Adding the extension assuming that $this->twig is your Twig Environment:
$this->twig->addExtension(new DateWithFallback($this->twig));
I'm saving all datetime data (created_at, updated_at and a custom datetime column: appointment_at) in UTC format.
I would like to retrieve all these dates, and convert them to the user's timezone for display.
I've added these functions to my model thinking this would retrieve the datetime in the user's timezone:
public function getCreatedAtAttribute($value)
{
return $created_at->timezone(Auth::user()->timezone);
}
public function getUpdatedAtAttribute($value)
{
return $updated_at->timezone(Auth::user()->timezone);
}
public function getAppointmentAtAttribute($value)
{
return $appointment_at->timezone(Auth::user()->timezone);
}
But I get the following error:
Call to a member function timezone() on a non-object
I'm guessing there's an error in my syntax. Am I missing something? Thanks
UPDATE #1 Made corrections to Note Model and added complete Model below (including corrections by #bernie)
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Note extends Model {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'notes';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['client_id', 'business_id', 'note'];
/**
* Note belonging to client.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function client()
{
return $this->belongsTo('App\Client');
}
public function getCreatedAtAttribute($value)
{
return $this->created_at->timezone(Auth::user()->timezone);
}
public function getUpdatedAtAttribute($value)
{
return $this->updated_at->timezone(Auth::user()->timezone);
}
public function getAppointmentAtAttribute($value)
{
return $this->appointment_at->timezone(Auth::user()->timezone);
}
Database Model definition
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateNotesTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('notes', function(Blueprint $table)
{
$table->increments('id');
$table->integer('client_id')->unsigned();
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$table->integer('business_id')->unsigned();
$table->foreign('business_id')->references('id')->on('businesses')->onDelete('cascade');
$table->text('note');
$table->dateTime('appointment_at');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('notes');
}
}
More info about the error (after updates by bernie)
ErrorException in Note.php line 40: Undefined property: App\Note::$created_at
in Note.php line 40
at HandleExceptions->handleError('8', 'Undefined property: App\Note::$created_at', '/var/www/html/laravel5/app/Note.php', '40', array()) in Note.php line 40
at Note->getCreatedAtAttribute('2015-09-22 12:50:20') in Model.php line 2669
at Model->mutateAttribute('created_at', '2015-09-22 12:50:20') in Model.php line 2592
at Model->getAttributeValue('created_at') in Model.php line 2557
at Model->getAttribute('created_at') in Model.php line 3263
at Model->__get('created_at') in NotesController.php line 54
at NotesController->show('1')
Solution
public function getCreatedAtAttribute($value)
{
$date = $this->asDateTime($value);
return $date->timezone(\Auth::user()->timezone);
}
or
public function getCreatedAtAttribute($value)
{
$format = $this->getDateFormat();
return Carbon::createFromFormat($format, $value, 'UTC')->setTimezone( \Auth::user()->timezone);
}
Both of these solutions worked for me.
Ah now I see! I realized you're using Accessors and Mutators. You're also overriding the built-in date mutators used for created_at and other dates fields.
So, try this instead :
public function getCreatedAtAttribute($value)
{
// asDateTime() is the built-in date mutator.
$date = $this->asDateTime($value);
return $date->timezone(Auth::user()->timezone);
}
public function getUpdatedAtAttribute($value)
{
$date = $this->asDateTime($value);
return $date->timezone(Auth::user()->timezone);
}
public function getAppointmentAtAttribute($value)
{
$date = $this->asDateTime($value);
return $date->timezone(Auth::user()->timezone);
}
The #depends annotation allows to express dependencies between tests:
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testOne()
{
// ...
return $client;
}
/**
* #depends testOne
*/
public function testTwo(Client $client)
{
// ...
}
}
If I want to return several values, I can return an array of values, such as:
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testOne()
{
// ...
return array($client, $node);
}
/**
* #depends testOne
*/
public function testTwo(array $items)
{
list ($client, $node) = $items;
// ...
}
}
While it works fine, the problem with this approach is that I lose the type hinting of my IDE, and would have to manually annotate the $client and $node variables so that it understands them properly.
What I'd like to do instead, is to explicitly use the return values as separate parameters in the second test:
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testOne()
{
// ...
return array($client, $node);
}
/**
* #depends testOne
*/
public function testTwo(Client $client, Node $node)
{
// ...
}
}
Is that possible?
I looked a while back and I think the answer is no. This is more of a clarity problem than anything. Because PHP only allows you to return one variable, you would need to introduce extra annotations logic at the PHPUnit level to tell it that what you are returning is not one value but an array of values.
On a side note I have found that returning objects with #depends can get tricky. If for example you had two tests that both depends on sets say #depends createUser. The result is a reference. Then testOne changes the user object it is given because it's a reference testTwo will now get a user object that has been changed.
I originally thought the idea of #depends returning values was awesome, but in practice I've stopped using it.