Sinon create stub instance - include protected properties - sinon

My question is how does one include protected properties when creating a stub instance.
In my jest test I have:
const sandbox = createSandbox();
let manager: SinonStubbedInstance<EntityManager>;
let repo: Repo;
beforeEach(() => {
manager = sandbox.createStubInstance(EntityManager);
repo = new Repo(manager);
});
afterEach(() => sandbox.restore());
Which is attempting to make a stub of:
export declare class EntityManager {
/**
* Connection used by this entity manager.
*/
readonly connection: Connection;
/**
* Custom query runner to be used for operations in this entity manager.
* Used only in non-global entity manager.
*/
readonly queryRunner?: QueryRunner;
/**
* Once created and then reused by en repositories.
*/
protected repositories: Repository<any>[];
/**
* Plain to object transformer used in create and merge operations.
*/
.......
}
So I don't seem to be able to have readonly properties and protected properties included in the stub.
At the "repo = new Repo(manager);" line.
The above code yields the following exception:
Argument of type 'SinonStubbedInstance<EntityManager>' is not assignable to parameter of type 'EntityManager'.
Property 'repositories' is missing in type 'SinonStubbedInstance<EntityManager>'.ts(2345)
Is there anyway to tell Sinon to include the properties?
Any help would be most appreciated.

I solved this problem with
repo = new Repo(manager as any);

I don't know what in your case the Repo does with the EntityManager, also it is not completely clear to me what you want to test here.. so based on this my answer is a bit generic, but maybe it points you in the right direction.
My idea: Maybe you should decouple them. I would approach it the following way:
Create a getter in the EntityManager that gets all repos, name it for example getRepos()
Create a mocked array that contains some Repos... const mockedRepos;
Mock the getter of the EntityManager with your stub instance returning your mocked data:
manager.getRepos.returns(mockedRepos);
This way you don't need the protected repositories var in your test.

Related

Passing an attribute to Doctrine AbstractIdGenerator

I need to pass LoggerInterface to the MyGenerator used in #ORM\CustomIdGenerator(class=MyGenerator::class)
Doctrine does not use the symfony container to instantiate the generator and I'm ending up with an Exception Too few arguments to function How can I use the LoggerInterface in my id generator ?
Unfortunately, it's not possible to inject LoggerInterface into MyGenerator class, as it's not a service and has nothing to do with the service container. However, in AbstractIdGenerator there is an EntityManager available, which provides a foundation for a workaround solution in order to propagate logs via a database table. After that, you'll be able to fetch log messages from a table via cronjob and write proper logs or do whatever you need.
class MyGenerator extends AbstractIdGenerator
{
public function generate(EntityManager $em, $entity)
{
$identifier = '...'; // generate an identifier
// push a log message to a db
$query = $em->createQuery('INSERT INTO db.logger (id, message, created_at) VALUES (null, :message, NOW())');
$query->setParameter('message', 'Log message...');
$query->execute();
return $identifier;
}
}

preUpdate() siblings manage into tree: how to break ->persist() recursion?

Let's say I've got an entity like this
class FooEntity
{
$id;
//foreign key with FooEntity itself
$parent_id;
//if no parent level =1, if have a parent without parent itself = 2 and so on...
$level;
//sorting index is relative to level
$sorting_index
}
Now I would like on delete and on edit to change level and sorting_index of this entity.
So I've decided to take advantage of Doctrine2 EntityListeners and I've done something similar to
class FooListener
{
public function preUpdate(Foo $entity, LifecycleEventArgs $args)
{
$em = $args->getEntityManager();
$this->handleEntityOrdering($entity, $em);
}
public function preRemove(Foo $entity, LifecycleEventArgs $args)
{
$level = $entity->getLevel();
$cur_sorting_index = $entity->getSortingIndex();
$em = $args->getEntityManager();
$this->handleSiblingOrdering($level, $cur_sorting_index, $em);
}
private function handleEntityOrdering($entity, $em)
{
error_log('entity to_update_category stop flag: '.$entity->getStopEventPropagationStatus());
error_log('entity splobj: '.spl_object_hash($entity));
//code to calculate new sorting_index and level for this entity (omitted)
$this->handleSiblingOrdering($old_level, $old_sorting_index, $em);
}
}
private function handleSiblingOrdering($level, $cur_sorting_index, $em)
{
$to_update_foos = //retrieve from db all siblings that needs an update
//some code to update sibling ordering (omitted)
foreach ($to_update_foos as $to_update_foo)
{
$em->persist($to_update_foo);
}
$em->flush();
}
}
The problem here is pretty clear: if I persist a Foo entity, preUpdate() (into handleSiblingOrdering function) trigger is raised and this cause an infinite loop.
My first idea was to insert a special variable inside my entity to prevent this loop: when I started a sibling update, that variable is setted and before executing the update code is checked. This works like a charm for preRemove() but not for preUpdate().
If you notice I'm logging spl_obj_hash to understand this behaviour. With a big surprise I can see that obj passed to preUpdate() after a preRemove() is the same (so setting a "status flag" is a fine) but the object passed to preUpdate() after a preUpdate() isn't the same.
So ...
First question
Someone could point me in the right direction to manage this situation?
Second question
Why doctrine needs to generate different objects if two similar events are raised?
I've founded a workaround
Best approach to this problem seem to create a custom EventSubscriber with a custom Event dispatched programmatically into controller update action.
That way I can "break" the loop and having a working code.
Just to make this answer complete I will report some snippet of code just to clarify che concept
Create custom events for your bundle
//src/path/to/your/bundle/YourBundleNameEvents.php
final class YourBundleNameEvents
{
const FOO_EVENT_UPDATE = 'bundle_name.foo.update';
}
this is a special class that will not do anything but provide some custom events for our bundle
Create a custom event for foo update
//src/path/to/your/bundle/Event/FooUpdateEvent
class FooUpdateEvent
{
//this is the class that will be dispatched so add properties useful for your own logic. In my example two properties could be $level and $sorting_index. This values are setted BEFORE dispatch the event
}
Create a custom event subscriber
//src/path/to/your/bundle/EventListener/FooSubscriber
class FooSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(YourBundleNameEvents::FooUpdate => 'handleSiblingsOrdering');
}
public function handleSiblingsOrdering(FooUpdateEvent $event)
{
//I can retrieve there, from $event, all data I setted into event itself. Now I can run all my own logic code to re-order siblings
}
}
Register your Subscriber as a service
//app/config/config.yml
services:
your_bundlename.foo_listener:
class: Your\Bundle\Name\EventListener\FooListener
tags:
- { name: kernel.event_subscriber }
Create and dispatch events into controller
//src/path/to/your/bundle/Controller/FooController
class FooController extends Controller
{
public function updateAction()
{
//some code here
$dispatcher = $this->get('event_dispatcher');
$foo_event = new FooEvent();
$foo_event->setLevel($level); //just an example
$foo_event->setOrderingIndex($ordering_index); //just an examle
$dispatcher->dispatch(YourBundleNameEvents::FooUpdate, $foo_event);
}
}
Alternative solution
Of course above solution is the best one but, if you have a property mapped into db that could be used as a flag, you could access it directly from LifecycleEventArgs of preUpdate() event by calling
$event->getNewValue('flag_name'); //$event is an object of LifecycleEventArgs type
By using that flag we could check for changes and stop the propagation
You are doing wrong approach by calling $em->flush() inside preUpdate, I even can say restricted by Doctrine action: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-implementing-listeners
9.6.6. preUpdate
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
EntityManager#flush() method.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
referential integrity at this point of the flush operation.

PHPUnit test if class methods were called

I have model class that calls mailer class inside one of its methods:
class someModel{
public function sendEmail($data){
$mailer = new Mailer();
$mailer->setFrom($data['from']);
$mailer->setTo($data['to']);
$mailer->setSubject($data['subject']);
return $mailer->send();
}
}
How can I test sendEmail method? Maybe I should mock mailer class and check if all these mailer methods were called in sendMail method?
Your help would be appreciated.
IMO wrapping the Mailer class does not solve the problem you're facing, which is you don't have control over the Mail instance being used.
The problem comes from creating the dependencies inside the object that needs them instead of injecting them externally like this:
class someModel{
private $mailer;
public function __construct(Mailer $mailer) {
$this->mailer = $mailer;
}
public function sendEmail($data){
$this->mailer->setFrom($data['from']);
$this->mailer->setTo($data['to']);
$this->mailer->setSubject($data['subject']);
return $this->mailer->send();
}
}
When creating the someModel instance, you must pass a Mail instance (which is an external dependency). And in the test you can pass a Mail mock that will check that the correct calls are being made.
Alternative:
If you feel that injecting a Mail instance is bad (maybe because there are lots of someModel instances), or you just can't change your code this way, then you could use a Services repository, that will keep a single Mail instance and that allows you to set it externally (again, in the test you would set a mock).
Try a simple one like Pimple.
I would (and have in my own code with Mailer!) wrap your instance of Mailer inside a class that you write. In other words, make your own Email class that uses Mailer under the hood. That allows you to simplify the interface of Mailer down to just what you need and more easily mock it. It also gives you the ability to replace Mailer seamlessly at a later date.
The most important thing to keep in mind when you wrap classes to hide external dependencies is keep the wrapper class simple. It's only purpose is to let you swap out the Email libraries class, not provide any complicated logic.
Example:
class Emailer {
private $mailer = new Mailer();
public function send($to, $from, $subject, $data) {
$this->mailer->setFrom($from);
$this->mailer->setTo($to);
...
return $mailer->send();
}
}
class EmailerMock extends Emailer {
public function send($to, $from, $subject, $data) {
... Store whatever test data you want to verify ...
}
//Accessors for testing the right data was sent in your unit test
public function getTo() { ... }
...
}
I follow the same pattern for all classes/libraries that want to touch things external to my software. Other good candidates are database connections, web services connections, cache connections, etc.
EDIT:
gontrollez raised a good point in his answer about dependency injection. I failed to explicitly mention it, but after creating the wrapper the way you would want to use some form of dependency injection to get it into the code where you want to use it. Passing in the instance makes it possible to setup the test case with a Mocked instance.
One method of doing this is passing in the instance to the constructor as gontrollez recommends. There are a lot of cases where that is the best way to do it. However, for "external services" that I am mocking I found that method became tedious because so many classes ended up needing the instance passed in. Consider for example a database driver that you want to Mock for your tests, but you use in many many different classes. So instead what I do is create a singleton class with a method that lets me mock the whole thing at once. Any client code can then just use the singleton to get access to a service without knowing that it was mocked. It looked something like this:
class Externals {
static private $instance = null;
private $db = null;
private $email = null;
...
private function __construct() {
$this->db = new RealDB();
$this->mail = new RealMail();
}
static function initTest() {
self::get(); //Ensure instance created
$db = new MockDB();
$email = new MockEmail();
}
static function get() {
if(!self::$instance)
self::$instance = new Externals();
return self::$instance;
}
function getDB() { return $this->db; }
function getMail() { return $this->mail; }
....
}
Then you can use phpunit's bootstrap file feature to call Externals::initTest() and all your tests will be setup with the mocked externals!
First, as RyanW says, you should write your own wrapper for Mailer.
Second, to test it, use a mock:
<?php
class someModelTest extends \PHPUnit_Framework_TestCase
{
public function testSendEmail()
{
// Mock the class so we can verify that the methods are called
$model = $this->getMock('someModel', array('setFrom', 'setTo', 'setSubject', 'send'));
$controller->expects($this->once())
->method('setFrom');
$controller->expects($this->once())
->method('setTo');
$controller->expects($this->once())
->method('setSubject');
$controller->expects($this->once())
->method('send');
$model->sendEmail();
}
}
The above code is untested, but it basically mocks the someModel class, creating dummy functions for each each function called within sendEmail. It then tests to make sure each of the functions called by sendEmail is called exactly once when sendEmail is called.
See the PHPUnit docs for more info on mocking.

How can I add to a collection of a new obect?

i've got an object with a ManyToMany-relation to an other object via a collection in Typo3 Flow. After create a new instance of that object (which is successfully added to the repository) I can simply add to this collection.
Code snippet of abc Model:
/**
* #var \Doctrine\Common\Collections\Collection<[..]\Domain\Model\Xyz>
* #ORM\ManyToMany(targetEntity="[..]\Domain\Model\Xyz")
*/
protected $xyzs;
[...]
public function getXYZs() {
return $this->xyzs;
}
public function addXYZ([..]\Domain\Model\Xyz $xyz) {
if(!$this->xyzs->contains($xyz))
$this->xyzs->add($xyz);
}
public function removeXYZ([..]\Domain\Model\Xyz $xyz) {
if($this->xyzs->contains($xyz))
$this->xyzs->removeElement($xyz);
}
The problem is that I can't add to this collection before I add it to the repository. (That happens because of non-existing foreign keys I guess).
Code snippet of abc controller (doesn't work!):
public function addAction([...]\$newABC)
{
[...]
$this->abcRepository->add($newABC);
//the line below returns "can't use contains() / add() on a non-object"
$newABC->addXYZ($someXYZ);
[...]
}
The xyz collection doesn't exist in the abc controller until the addAction() is finished completely. But how can I add to this collection before the addAction() is done?
It probably returns a no object error because collections are arrays of objects.
public function addXYZ([..]\Domain\Model\Xyz $xyz) {
$this->xyzs[] = $xyz;
}
The final solution needs a little work arround:
I take $newABC, $someXYZ and some reference stuff via a redirect to a PROTECTED! function in the same controller. (if not protected you could call it with url)
There the persistence manager allready persisted the $newABC. So there I easily can add the $someXYZ and finaly redirect it with my reference to the place I like to go.
Done.

ReferenceRepository strips related entities when calling getReference()

I am writing a Symfony 2 unit test that relies heavily on data fixtures. As a shortcut, I wired up a method that will give me access to the fixture loader's ReferenceRepository so that I can access shared entities in my tests.
However, when I pull an object out of the ReferenceRepository, it has no relations, even though I persist them in the data fixture.
The weird part is, there is some code in ReferenceRepository that appears to be stripping those relations out, and I don't understand why it is doing this (let alone how to prevent it).
As an example, here is what a data fixture looks like:
public function load(ObjectManager $manager)
{
$project = new Project();
// ... populate fields ...
/* Add one detail field to the Project. */
$detail = new ProjectDetail();
// ... populate fields ...
$project->addDetail($detail);
$manager->persist($project);
$manager->flush();
$this->addReference('project-onedetail', $project);
}
In my test case, I am doing something (more or less) like this:
$project =
$this->fixtureLoader->getReferenceRepository()
->getReference('project-onedetail');
When I call the method in the test case to grab this Project object, I notice some weird behavior:
From Doctrine\Common\DataFixtures\ReferenceRepository (comments added):
public function getReference($name)
{
$reference = $this->references[$name];
// At this point, $reference contains the Project object with related ProjectDetail.
// It would be awesome if the method would just return $reference...
$meta = $this->manager->getClassMetadata(get_class($reference));
$uow = $this->manager->getUnitOfWork();
if (!$uow->isInIdentityMap($reference) && isset($this->identities[$name])) {
// ... but instead it goes into this conditional....
$reference = $this->manager->getReference(
$meta->name,
$this->identities[$name]
);
// ... and now $reference->getDetails() is empty! What just happened??
$this->references[$name] = $reference; // already in identity map
}
return $reference;
}
What's going on in ReferenceRepository->getReference()? Why are the related objects getting removed from $reference, and how do I prevent that?
What's Going On
After the fixture loader runs, it clears out the UnitOfWork's identity map.
See \Doctrine\Common\DataFixtures\Executor\AbstractExecutor:
public function load(ObjectManager $manager, FixtureInterface $fixture)
{
...
$fixture->load($manager);
$manager->clear();
}
As a result, the condition !$uow->isInIdentityMap($reference) in ReferenceRepository->getReference() will always evaluate to false after the fixture loader has finished.
The Workaround
You can work around this by clearing out ReferenceRepository->$identities. Unfortunately, you don't have direct access to this array, so you'll need to do something slightly kludgy like:
/* #kludge The fixture loader clears out its UnitOfWork object after
* loading each fixture, so we also need to clear the
* ReferenceRepository's identity map.
*/
$repository = $this->fixtureLoader->getReferenceRepository();
$identities = array_keys($repository->getIdentities());
foreach($identities as $key)
{
$repository->setReferenceIdentity($key, null);
}
However, if you do that, you may run into some nasty ORMInvalidArgumentExceptions if you set related objects in your test fixtures:
Doctrine\ORM\ORMInvalidArgumentException: A new entity was found through the relationship '...' that was not configured to cascade persist operations for entity: url. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
The Solution
Ultimately, if you want this to work properly, you'll need to change the behavior of the fixture executor that you use in your test cases so that it does not clear the manager after loading fixtures:
/** Executes data fixtures for unit tests.
*/
class TestExecutor extends ORMExecutor
{
/** Load a fixture with the given persistence manager.
*
* #param ObjectManager|EntityManager $manager
* #param FixtureInterface $fixture
*/
public function load(ObjectManager $manager, FixtureInterface $fixture)
{
/** #kludge Unfortunately, we have to copy-paste a bit of code.
*
* The only difference between this method and AbstractExecutor->load()
* is that we don't call $manager->clear() when we're done loading.
*/
if($this->logger)
{
$prefix = '';
if($fixture instanceof OrderedFixtureInterface)
{
$prefix = sprintf('[%d] ', $fixture->getOrder());
}
$this->log('loading ' . $prefix . get_class($fixture));
}
// additionally pass the instance of reference repository to shared fixtures
if($fixture instanceof SharedFixtureInterface)
{
$fixture->setReferenceRepository($this->referenceRepository);
}
$fixture->load($manager);
/* Do NOT clear the unit of work; we will keep managed entities so that
* they are available to tests.
*/
}
}

Resources