I have several unit tests failing on my local machine because DateTime values differ:
4) Tests\AppBundle\Form\AssetStatusTypeTest::testValidForm with data set #0 (array('active'))
Failed asserting that two objects are equal.
--- Expected
+++ Actual
## ##
'status' => 'active'
'type' => null
'amount' => null
- 'createdAt' => 2018-09-20T20:34:47.047520+0200
+ 'createdAt' => 2018-09-20T20:34:47.047870+0200
'updatedAt' => null
'contract' => null
'admin' => null
The test:
public function testValidForm($data)
{
$form = $this->factory->create(AssetStatusType::class);
$object = Entity::fromArray(new Asset(), $data);
$form->submit($data);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($object, $form->getData());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($data) as $key) {
$this->assertArrayHasKey($key, $children);
}
}
Which is more or less copied from the docs.
Now, the createdAt field is set like this:
public function __construct()
{
if (empty($this->createdAt)) {
$this->createdAt = new \DateTime();
}
}
My main point is: This test is passing on our Jenkins. It does not locally on several developer machines. My first impulse was to check ini timezone settings.
I wonder how this is even possible. If I get it right, expected is when object was created and actual is when the form is submitted. So both objects can never have the same createdAt timestamp. Unless precision is probably super low.
The two machines are running different versions of PHP. PHP7.1 (and above) includes microseconds when it creates a DateTime object, but code running on PHP 5.x and 7.0, won't.
There are two ways to work with this:
Don't compare exact DateTimes, but convert them to seconds with $datetimeObj->format('U');. You'll still get occasional test failures when one test is created at say 1.99998 and the next call to create a datetime is at 2.0001, and so the test still fails when converted to seconds.
Use 'Clock Mocking'. Using some interesting PHP namespace tricks, the global time() function is overridden (sleep() too). You have to create new DateTime object to make sure they actually use the new version of the time() function, but the clock would effectively stop - and sleep()'s just turn into something more like $time += $seconds; - which also means that a sleep(3600); takes effective zero time.
The symfony/phpunit-bridge ClockMock.php can be used just as a library if you don't want to include it as a listener in your PHPunit configuration.
Related
I want to functional-test a Symfony's command I'm creating. I'm taking advantage of the Question helper class, by associating a personal validator:
$helper = $this->getHelper('question');
$question = new Question('Enter a valid IP: ');
$question->setValidator($domainValidator);
$question->setMaxAttempts(2);
the tests I'm performing are functionals, so in order to mock the interaction I added something like the following to my PHPUnit's test class. Here's an excerpt:
public function testBadIpRaisesError()
{
$question = $this->createMock('Symfony\Component\Console\Helper\QuestionHelper');
$question
->method('ask')
->will($this->onConsecutiveCalls(
'<IP>',
true
));
...
}
protected function createMock($originalClassName)
{
return $this->getMockBuilder($originalClassName)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->getMock();
}
of course this mock is more than ok when I'm testing something that goes beyond the Question helper, but in this case what I'd like to do is testing the whole thing in order to make sure the validator is well written.
What's the best option in this case? Unit testing my validator is OK, but I would like to functional-testing it as a black box by the user point of view
The Console component provides a CommandTester for precisely this use case: https://symfony.com/doc/current/console.html#testing-commands. You probably want to do something like this:
<?php
class ExampleCommandTest extends \PHPUnit\Framework\TestCase
{
public function testBadIpRaisesError()
{
// For a command 'bin/console example'.
$commandName = 'example';
// Set up your Application with your command.
$application = new \Symfony\Component\Console\Application();
// Here's where you would inject any mocked dependencies as needed.
$createdCommand = new WhateverCommand();
$application->add($createdCommand);
$foundCommand = $application->find($commandName);
// Create a CommandTester with your Command class processed by your Application.
$tester = new \Symfony\Component\Console\Tester\CommandTester($foundCommand);
// Respond "y" to the first prompt (question) when the command is invoked.
$tester->setInputs(['y']);
// Execute the command. This example would be the equivalent of
// 'bin/console example 127.0.0.1 --ipv6=true'
$tester->execute([
'command' => $commandName,
// Arguments as needed.
'ip-address' => '127.0.0.1',
// Options as needed.
'--ipv6' => true,
]);
self::assert('Example output', $tester->getDisplay());
self::assert(0, $tester->getStatusCode());
}
}
You can see some more sophisticated working examples in a project I'm working on: https://github.com/php-tuf/composer-stager/blob/v0.1.0/tests/Unit/Console/Command/StageCommandTest.php
I've got a controller action that's supposed to be looking for semi-duplicate entries in a collection, and removing them from that Entity's list. New ones should not have an ID yet, and existing ones do, so I'm running findOneBy() with an array of parameters to match on (leaving out ID).
I am baffled and deeply troubled by the error I am getting, where it finds the wrong entity! I've got some relevant code below, I hope this is just a dream or a silly mistake, I can't reproduce the output on my own windows development environment, but my co-worker was testing on his mac, and was getting errors, so I went on his machine and did some quick echoing to see what was going on. See the code and the result below.
CODE
foreach($entity->getTechnicians() as $tech) {
//find new addtions, see if they exist
if (!$tech->getId()) {
echo "Technician " . $tech->getFirstName() . " has no ID, trying to find one that does<br/>";
$found = $em->getRepository('TechnicianBundle:Technician')->findOneBy(array(
'firstName' => $tech->getFirstName(),
'lastName' => $tech->getLastName(),
'email' => $tech->getEmail(),
'residentEngineer' => $tech->getResidentEngineer(),
'officeNumber' => $tech->getOfficeNumber()
));
//if one with an ID already exists
if ($found) {
echo "found technician " . $found->getFirstName() . " that already has id " . $found->getId() . "<br/>";
...
OUTPUT
Technician Four has no ID, trying to find one that does
found technician khjvuov that already has id 7
Probably it's not a findOneBy() issue.
If without add/remove calls it works, it may be caused by modified current pointer of ArrayCollection so when $tech->getFirstName() is called it actually points to another entry.
You may try to iterate your collection like this:
$iterator = $entity->getTechnicians()->getIterator();
while ($iterator->valid()) {
$tech = $iterator->current();
... $entity->addTechnician()/$entity->removeTechnician()
$iterator->next();
}
It will create new ArrayIterator object, so you can modify underlying object preserving ArrayCollection's internal pointer.
This may be obsolete if you are running PHP7 (reference - Note box)
Ultimately, the findOneBy() method uses the load() method of the Doctrine ORM.
In the doc, the parameter $criteria is described as such :
#param array $criteria The criteria by which to load the entity.
You should probably dig deeper into Doctrine API to make sure, but there is a big chance that in the array you are passing as an argument, only the first key => value pair is taken into account (in your example, 'firstName' => $tech->getFirstName()).
If this is the case, you'll probably need to add your own custom method in your entity repository, one that would allow you to query your database with more advanced statements (using OR/AND or LIKE() in SQL for instance).
I'm using symfony + Doctrine and I'm stuck with a problem:
I cloned an existing object and I would like to change a FK on the clone. It should be like that:
$dafCloned = clone $daf;
$dafState = $dafStateRepository->findOneBy(
array(
'name' => 'saved',
'dafType' => 'invoice',
'company' => $daf->getSeller(),
));
$dafCloned->setDafState($dafState);
var_dump($dafState->getId());
var_dump($dafCloned->getDafState()->getId());
$this->em->persist($dafCloned);
$this->em->flush();
As you may have noticed, I got 2 var_dump here. Here are the print of the Custom Command calling this code :
int(5500)
int(5499)
5500 is the id I should have in db for $dafCloned, 5499 is the id I have for $daf.
I'd like to know WHY I got different id...My dafState should be the same. I'm probably missing something really stupid but I'm stuck on it since 9am...I even tried to delete all caches we have, moving flush() and persist() but cant help :s
EDIT : added the setDafState() method if needed, but this is basic :
public function setDafState(DafState $dafState) {
$this->dafState = $dafState;
return $this;
}
EDIT2 :
Here getDafState() :
/**
* Get dafState
*
* #return MyPath\Entity\DafState
*/
public function getDafState() {
return $this->dafState;
}
If you need more code sample, just ask for it, I'll edit ;)
For the object, both are huge (Doctrine Object) and i can't find any way to get what could be useful :s. I cant grep dafState on $daf Object, output is still huge.
EDIT 3 :
if ($daf->getId() == 8902) // daf test which should be duplicated
var_dump($dafCloned->getDafState() === $dafState);
output
bool(true)
$dafCloned = clone $daf; // Here your clone is the same object as the old one
$dafState = $dafStateRepository->findOneBy( // Here you get some fresh object
array(
'name' => 'saved',
'dafType' => 'invoice',
'company' => $daf->getSeller(),
));
$dafCloned->setDafState($dafState); // Because this object is still managed by the entity manager it will set the $dafState on the old object (tracked by Id most likely)
var_dump($dafState->getId()); // Show the Id on the fresh object
var_dump($dafCloned->getDafState()->getId()); // Show the Id on the old object
$this->em->persist($dafCloned); // overwrite the old object
$this->em->flush();
This Post will be helpful to you: How to re-save the entity as another row in Doctrine 2
I will update my answer if this doesn't solve your issue
Here we go.
Thanks to #cheesemacfly i find out i have a prePersistListener which was resetting my dafState !
So, next time you have something weird looking like the above problem, check your listener !
I need help please.
I'm using Smarty with PHPUnit, and I have troubles.
For example:
In some checks, I call fetch function many times, and I only receive right the first call, the others only return empty. Why???
I let some code here as an example:
/**
* #dataProvider provider_test
*/
public function test_field($field) {
// with this I instance smarty
$front = $this->get_template();
$front->assign('function', 'fb_user_field');
$front->assign('field', $field);
// this fetch only return right widh the first value of field
$result = $front->fetch('tests/generic.tpl');
$this->assertNotNull($result);
}
public function provider_test() {
return array(
array('field' => 'subdomain'),
array('field' => 'login')
);
}
I check $field and in every iteration receive the correct value, but after the first one, fetch return only empty.
Why???? Thanks!!!
My suggestion is to use the MakeGood or similar to step by step debug your php code. Otherwise it is guess and check till you die!
Here's some links
http://blog.loftdigital.com/running-phpunit-tests-in-eclipse-pdt
http://www.youtube.com/watch?v=1qnWL52wt58
I used to spend hours guess and check with phpunit until I discovered MakeGood. Hope it helps!
I'm writing a wrapper class for my drupal 7 site which lets me connect to and query my phpbb database.
When connecting to an external data source (as per drupal documentation) you have set the active db, run the query, then set the active db back to the default.
e.g.
db_set_active('phpbb');
$result = db_query($sql,$args,$opts);
db_set_active();//back to default
But is there any way to use drupal's database wrapper to create a brand new connection which can be permanently set to the new database without having to do this switching back-and-forth nonsense? surely we can handle connections to multiple databases concurrently.
I have done some googling but haven't found anybody trying to do this as yet.
Typical. 5 minutes after posting i figure it out... so, for future googlers:
Basically, you don't use db_query, instead you run the query on your connection without setting the active link.
you can figure this out by looking at how db_query works:
http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_query/7
So it looks like this:
$target='default';
$key = 'phpbb';
$phpbb = Database::getConnection($target,$key);
$result = $phpbb->query($sql,$args,$opts);
This assumes you have a database configured in your settings.php like the following:
$databases['phpbb']['default'] = array(
'driver' => 'mysql',
'database' => 'forum',
'username' => 'username',
'password' => 'password',
'host' => 'mysql.host.com',
'prefix' => 'phpbb3_'
);
Database::addConnectionInfo() perhaps?
This method allows the addition of new connection credentials at
runtime. Under normal circumstances the preferred way to specify
database credentials is via settings.php. However, this method allows
them to be added at arbitrary times, such as during unit tests, when
connecting to admin-defined third party databases, etc.
If the given key/target pair already exists, this method will be
ignored.
The definition for getConnection cites a different order for arguments than used above.
function getConnection($target = 'default', $key = NULL)
This is sadly different from Database::addConnectionInfo() which is
public static function addConnectionInfo($key, $target, $info)
Also, on DB_select, the $key is not a parameter, though it is in the options array:
function db_select($table, $alias = NULL, array $options = array()) {
if (empty($options['target'])) {
$options['target'] = 'default';
}
return Database::getConnection($options['target'])->select($table, $alias, $options);
}
while
final public static function getConnection($target = 'default', $key = NULL) {
so this implies that the 'master' or 'slave' or 'default' is always used as set, but not the key to the alternative database/schema, requiring the db_set_active('...'); and db_set_active(); around the db_select.
Since calls to other dbs can easily be required within the processing of the db_select (such as devel calls or calls in alters), this is inflexible design. Changing this call:
return Database::getConnection($options['target'])->select($table, $alias, $options);
to add the Key parameter (it is already spec'd as an argument!!) is needed but insufficient so far as I can now see.