I am new to PHPunit and while learning it with sample classes, I am getting error. The error is that, the Object $this->sysman, that is initialized in setUp() becomes null after execution of the 1st test. Below is code, which I am trying. Please let me know what is mistake here. Thanks.
class PersonTest extends TestCase {
/** #var SysManService $sysman */
protected $sysman;
protected $personModel;
public function setUp(){
/** #var Adapter $adapter */
$adapter = $this->prophesize(Adapter::class)->reveal();
/** #var LoggerService $logger */
$logger = $this->prophesize(LoggerService::class)->reveal();
/** #var ContainerInterface $container */
$container = $this->prophesize(ContainerInterface::class);
$config = (array)$container->get('config');
$this->sysman = new SysManService($adapter, $logger, $config);
//$sysman = new SysManService($adapter, $logger, $config);
$this->personModel = new Person($this->sysman);
}
protected function tearDown(){ }
public function testPropertyUserNameExists() {
$this->assertObjectHasAttribute('userName', $this->personModel);
}
public function testPropertyPasswordExists() {
$this->assertObjectHasAttribute('password', $this->personModel);
}}
Related
I have a Symfony app whereby a user can send a friend request, which will be received by the other user who can then accept or decline the request thereby creating the relationship. I have an idea of how to implement this, however, my inexperience has to lead me to a wall.
Friends Entity
/**
* #ORM\Entity()
*/
class Friends
{
const STATUS_PENDING = 'STATUS_PENDING';
const STATUS_FRIEND = 'STATUS_FRIEND';
const STATUS_BLOCKED = 'STATUS_BLOCKED';
const DEFAULT_STATUS = [self::STATUS_PENDING];
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="myFriends")
* #ORM\Id
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="friendsWithMe")
* #ORM\Id
*/
private $friend;
/**
* #ORM\Column(type="simple_array")
*/
private $status;
public function __construct()
{
$this->status = self::DEFAULT_STATUS;
}
public function addFriend(User $user, User $friend): self
{
$userID = $user->getId();
$friendID = $friend->getId();
switch ($userID)
{
case $userID > $friendID:
$this->setUser($friend);
$this->setFriend($user);
break;
case $userID < $friendID:
$this->setUser($user);
$this->setFriend($friend);
break;
case $userID === $friendID:
break;
}
return $this;
}
}
User Entity
/**
* #ORM\Entity()
*/
class User implements UserInterface
{
/**
* Many Users have Many Users.
* The people who I think are my friends.
*
* #ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
private $friendsWithMe;
/**
* Many Users have many Users.
* The people who think that I’m their friend.
*
* #ORM\OneToMany(targetEntity="Friends", mappedBy="user")
*/
private $myFriends;
public function __construct()
{
$this->roles = self::DEFAULT_ROLES;
$this->households = new ArrayCollection();
$this->myFriends = new ArrayCollection();
$this->friendsWithMe = new ArrayCollection();
}
}
The Controller looks like this:
private $entityManager;
private $friends;
private $user;
public function __construct(TokenStorageInterface $tokenStorage, EntityManagerInterface $entityManager)
{
$this->user = $tokenStorage->getToken()->getUser();
$this->friends = new Friends();
$this->entityManager = $entityManager;
}
/**
* #Route("/api/addFriend/{id}", name="addFriend", requirements={"id"="\d+"}, methods={"POST"})
* #ParamConverter("user", class="App:User")
*/
public function addFriend(User $user)
{
$loggedInUser = $this->user;
$add = $this->friends->addFriend($loggedInUser,$user);
$this->entityManager->persist($this->friends);
$this->entityManager->flush();
}
The issue is that When I try to use the controller. It fails at persist() and and the error reads:
Warning: get_class() expects parameter 1 to be object, int given
I believe the issue lies either with the way I am using the user objects in the Friends class Or the way the friend's entity is made. I followed This to show me how to implement the many to many user/Friends relationship initially. Could anyone help guide me in the right direction, please? Thank you very much.
In a symfony 4 project, many services/controllers need log.
Trying to use the advantage of traits & autowire options given by symfony, I created a loggerTrait that will be passed to the different services.
namespace App\Helper;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
trait LoggerTrait
{
/** #var LoggerInterface */
private $logger;
/** #var array */
private $context = [];
/**
* #return LoggerInterface
*/
public function getLogger(): LoggerInterface
{
return $this->logger;
}
/**
* #required
*
* #param LoggerInterface|null $logger
*/
public function setLogger(?LoggerInterface $logger): void
{
$this->logger = $logger;
}
public function logDebug(string $message, array $context = []): void
{
$this->log(LogLevel::DEBUG, $message, $context);
}
...
}
(inspired by symfonycasts.com)
A service will be using this trait
namespace App\Service;
use App\Helper\LoggerTrait;
class BaseService
{
use LoggerTrait;
/** #var string */
private $name;
public function __construct(string $serviceName)
{
$this->name = $serviceName;
}
public function logName()
{
$this->logInfo('Name of the service', ['name' => $this->name]);
}
}
It works perfectly but I couldn't succeed to test it.
I tried to extend KernelTestCase in my test to mock an loggerInterface but I receive Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "Psr\Log\LoggerInterface" service is private, you cannot replace it which make perfect sens.
Here my test:
namespace App\Tests\Service;
use App\Service\BaseService;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class BaseServiceTest extends KernelTestCase
{
private function loggerMock()
{
return $this->createMock(LoggerInterface::class);
}
protected function setUp()
{
self::bootKernel();
}
/**
* #test
* #covers ::logName
*/
public function itShouldLogName()
{
// returns the real and unchanged service container
$container = self::$kernel->getContainer();
// gets the special container that allows fetching private services
$container = self::$container;
$loggerMock = $this->loggerMock();
$loggerMock->expect(self::once())
->method('log')
->with('info', 'Name of the service', ['name' => 'service_test']);
$this->logger = $container->set(LoggerInterface::class, $loggerMock);
$baseService = new BaseService('service_test');
var_dump($baseService->getLogger());
}
}
Is there a solution to test such a logger inside the service ?
You can override the service to be public (only for the test environment) in your config_test.yml as follows:
services:
Psr\Log\LoggerInterface:
public: true
This is commonly done for testing private services.
How to mock some service when test console command. I have some console command, in this command I get some service and I want mock this service
console command
const APP_SATISFACTION_REPORT = 'app:satisfactionrepor';
protected function configure()
{
$this
->setName(self::APP_SATISFACTION_REPORT)
->setDescription('Send Satisfaction Report');
}
/**
* #param InputInterface $input
* #param OutputInterface $output
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$container = $this->getContainer();
$serviceCompanyRepo = $container->get('app.repository.orm.service_company_repository');
$satisfactionReport = $container->get('app.services.satisfaction_report');
/** #var ServiceCompany $serviceCompany */
foreach ($serviceCompanyRepo->findAll() as $serviceCompany) {
try {
$satisfactionReport->sendReport($serviceCompany);
} catch (\Exception $e) {
$io->warning(sprintf(
'Failed to send satisfaction report for service company with ID %s',
$serviceCompany->getId()
));
}
}
}
and my tests
/** #var Console\Application $application */
protected $application;
protected $container;
/** #var BufferedOutput $output */
protected $output;
/**
* #var ServiceCompanyRepository
*/
private $serviceCompanyRepository;
prepare console command
public function setUp()
{
parent::setUp();
$entityManager = $this->getEntityManager();
$this->serviceCompanyRepository = $entityManager->getRepository(ServiceCompany::class);
static::bootKernel();
$this->container = static::$kernel->getContainer();
$this->application = new Console\Application(static::$kernel);
$this->application->setAutoExit(false);
$master = new SatisfactionReportCommand();
$this->application->add($master);
}
public function setUpMaster() {
$this->output = new BufferedOutput();
$this->application->run(new ArgvInput([
'./bin/console',
SatisfactionReportCommand::APP_SATISFACTION_REPORT,
]), $this->output);
}
public function testGetMasterOutput()
{
$this->loadFixture(ServiceCompany::class);
/** #var ServiceCompany[] $serviceCompanies */
$serviceCompanies = $this->serviceCompanyRepository->findAll();
$this->assertCount(2, $serviceCompanies);
$client = self::createClient();
mock service app.services.satisfaction_report
$service = $this->getMockService($serviceCompanies);
and set it in container
$client->getContainer()->set('app.services.satisfaction_report', $service);
$this->setUpMaster();
$output = $this->output->fetch();
}
protected function getMockService($serviceCompanies)
{
$service = $this->getMockBuilder(SatisfactionReport::class)
->setMethods(['sendReport'])
->disableOriginalConstructor()
->getMock();
$service
->expects($this->exactly(2))
->method('sendReport')
->withConsecutive(
[$serviceCompanies[0]],
[$serviceCompanies[1]]
);
return $service;
}
How to mock app.services.satisfaction_report? Set in container app.services.satisfaction_report not help me
I had same problem but I resolved it.
I have base class:
class TestCase extends WebTestCase
{
/** #var Application */
private $application;
private $mailServiceMock;
protected function setMailService(MailService $mailServiceMock): void
{
$this->mailServiceMock = $mailServiceMock;
}
protected function getApplication(): Application
{
static::bootKernel();
static::$kernel->getContainer()->get('test.client');
$this->setMocks();
$this->application = new Application(static::$kernel);
$this->application->setCatchExceptions(false);
$this->application->setAutoExit(false);
return $this->application;
}
protected function execute(string $action, array $arguments = [], array $inputs = []): CommandTester
{
$tester = (new CommandTester($this->getApplication()->find($action)))->setInputs($inputs);
$tester->execute($arguments);
return $tester;
}
private function setMocks(): void
{
if ($this->mailServiceMock) {
static::$kernel->getContainer()->set('mail', $this->mailServiceMock);
}
}
}
And test class
class SendEmailCommandTest extends TestCase
{
public function testExecuteSendingError(): void
{
$mailServiceMock = $this->getMockBuilder(MailService::class)->disableOriginalConstructor()
->setMethods(['sendEmail'])->getMock();
$mailServiceMock->method('sendEmail')->willThrowException(new \Exception());
$this->setMailService($mailServiceMock);
$tester = $this->execute(SendEmailCommand::COMMAND_NAME, self::NORMAL_PAYLOAD);
$this->assertEquals(SendEmailCommand::STATUS_CODE_EMAIL_SENDING_ERROR, $tester->getStatusCode());
}
}
As you can see I set mail service right after booting kernel.
And here you can see my services.yaml:
services:
mail.command.send.email:
autowire: true
class: App\Command\SendEmailCommand
arguments: ["#logger", "#mail"]
tags:
- {name: console.command, command: "mail:send.email"}
If you create the commands as a service, where the framework injects the services automatically (either autowired, or with an explicit argument list) into a constructor (tip: in the command, call the parent::__construct()), then the test can create whatever mock or other replacement service that matches the parameter typehint (or interface).
here is my example class:
class MainCommandTest extends IntegrationTestCase
{
/**
* #var MainCommand
*/
protected $subject;
/**
* #var Application
*/
protected $application;
/**
* sets test subject
*
* #return void
*/
public function setUp()
{
parent::setUp();
static::bootKernel();
$readStreams = new ReadStreams();
$udpStreamMock = $this->getMockBuilder(UdpStream::class)->disableOriginalConstructor()->setMethods(['readIncomingStreams'])->getMock();
$udpStreamMock->expects($this->once())->method('readIncomingStreams')->willReturn($readStreams);
static::$kernel->getContainer()->set(UdpStream::class, $udpStreamMock);
$application = new Application($this::$kernel);
$this->subject = $this->getService(MainCommand::class);
$application->add( $this->subject);
$this->application = $application;
}
/**
* Tests command in $subject command,
*
* #return void
*/
public function testCommand()
{
$command = $this->application->find( $this->subject->getName());
$commandTester = new CommandTester($command);
$commandTester->execute(
[
'command' => $this->subject->getName()
]
);
$this->stringContains($commandTester->getDisplay(true), 'finished');
$this->assertEquals($commandTester->getStatusCode(), 0);
}
}
I am using symfony 3. I already created some events and they work fine. But this new event is different. I must somehow send some additional parameters to subscriber. What is the best way to do that? I created phenstalk service to perform job. The controller dispatch an event like:
$dispatcher = $this->container->get('event_dispatcher');
$dispatcher->dispatch(Events::POWERPLANT_GET_DATA, new PowerPlantEvent($this->getPowerPlantAction($id)));
Call like this will not work, cuz I am missing some parameters.
and I have action in subscriber:
public function onPowerPlantGetData(PowerPlantEvent $event, $startDate, $endDate, $measurement, $field)
How to do that?
If you event uses optional parameters, you can make them optional in the constructor.
use Symfony\Component\EventDispatcher\Event;
final class PowerPlantEvent extends Event
{
/**
* #var PowerPlantAction
*/
private $powerPlantAction;
/**
* #var \DateTime
*/
private $startDate;
/**
* #var \DateTime
*/
private $endDate;
/**
* #var int
*/
private $measurement;
/**
* #var string
*/
private $field;
public function __construct(
$powerPlantAction,
$startDate = null,
$endDate = null,
$measurement = null,
$field = null
) {
$this->powerPlantAction = $powerPlantAction;
$this->startDate = $startDate;
$this->endDate = $endDate;
$this->measurement = $measurement;
$this->field = $field;
}
public function getPowerPlantAction()
{
return $this->powerPlantAction;
}
public function getStartDate()
{
return $this->startDate;
}
public function getEndDate()
{
return $this->endDate;
}
public function getMeasurement()
{
return $this->measurement;
}
public function getField()
{
return $this->field;
}
}
Then dispatching like this:
$this->eventDispatcher->dispatch(
Events::POWERPLANT_GET_DATA,
new PowerPlantEvent($this->getPowerPlantAction($id))
);
Or:
$this->eventDispatcher->dispatch(
Events::POWERPLANT_GET_DATA,
new PowerPlantEvent(
$this->getPowerPlantAction($id),
$startDate,
$endDate,
$measurement,
$field
)
);
In your subscriber just use those getters:
public function onPowerPlantGetData(PowerPlantEvent $powerPlantEvent)
{
$powerPlantEvent->getStartDate();
// ...
}
You should put all necessary data into Event itself and then you can access them in subscriber
For simplicity I made properties public and added all to __construct but it's up to you how you implement it. You might want to create new Event class for that
class PowerPlantEvent extends Event
{
/**
* #var \DateTime
*/
public $startDate;
/**
* #var \DateTime
*/
public $endDate;
/**
* #var Something
*/
public $measurement;
public $field;
/**
* PowerPlantEvent constructor.
*
* #param \DateTime $startDate
* #param \DateTime $endDate
* #param Something $measurement
* #param $field
*/
public function __construct(\DateTime $startDate, \DateTime $endDate, Something $measurement, $field)
{
$this->startDate = $startDate;
$this->endDate = $endDate;
$this->measurement = $measurement;
$this->field = $field;
}
}
and call it with
$dispatcher = $this->container->get('event_dispatcher');
$dispatcher->dispatch(
Events::POWERPLANT_GET_DATA,
new PowerPlantEvent($startDate, $endDate, $measurement, $field)
);
and access in subscriber (or event listener)
public function onPowerPlantGetData(PowerPlantEvent $event)
{
$startDate = $event->startDate; // you might want to implement getters for those
}
Attempting to build a user data fixture results in
Fatal error: Call to a member function get() on a non-object in
...\Tests\Repository\DataFixtures\LoadAdminUserData.php on line 35
line 35:
$discriminator = $this->container->get('pugx_user.manager.user_discriminator');
Complete fixture (w/ namespace edit)
<?php
//src\Vol\VolBundle\\DataFixtures\ORM\LoadAdminUserData
namespace Vol\VolBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Description of LoadAdminUserData
*
*/
class LoadAdminUserData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
private $container;
/**
* {#inheritDoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load(ObjectManager $manager)
{
$discriminator = $this->container->get('pugx_user.manager.user_discriminator');
$discriminator->setClass('Vol\VolBundle\Entity\Admin');
$userManager = $this->container->get('pugx_user_manager');
$admin = $userManager->createUser();
$admin->setUsername('bborko');
$admin->setEmail('bborko#bogus.info');
$admin->setPlainPassword('123456');
$admin->setEnabled(true);
$userManager->updateUser($admin, true);
$this->addReference('test-user', $admin);
}
public function getOrder()
{
return 3;
}
}
Test
<?php
//src\Vol\VolBundle\Tests\Repository
namespace Vol\VolBundle\Tests\Repository;
use Vol\VolBundle\DataFixtures\ORM\DoctrineTestCase;
/**
* Description of AdminUserRepositoryTest
*
* #author George
*/
class AdminUserRepositoryTest extends DoctrineTestCase
{
/**
* Set up repository test
*/
public function setUp()
{
$this->loadFixturesFromDirectory($this->dir);
}
/**
* Test finding all countries ordered
*/
public function testFindAll()
{
$focuses = $this->getRepository()->findAll();
$this->assertCount(1, $focuses, 'Should return 1 admin user');
}
/**
* Returns repository
*
* #return \Vol\VolBundle\Entity\Admin
*/
protected function getRepository()
{
return $this->em->getRepository('\Vol\VolBundle\Entity\Admin');
}
}
class DoctrineTestCase
<?php
//src\Vol\VolBundle\Tests\Repository
namespace Vol\VolBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* Class DoctrineTestCase
*
* This is the base class to load doctrine fixtures using the symfony configuration
*/
class DoctrineTestCase extends WebTestCase
{
/**
* #var \Symfony\Component\DependencyInjection\Container
*/
protected $container;
/**
* #var \Doctrine\ORM\EntityManager
*/
protected $em;
/**
* #var string
*/
protected $environment = 'test';
/**
* #var bool
*/
protected $debug = true;
/**
* #var string
*/
protected $entityManagerServiceId = 'doctrine.orm.entity_manager';
protected $dir;
/**
* Constructor
*
* #param string|null $name Test name
* #param array $data Test data
* #param string $dataName Data name
*/
public function __construct($name = null, array $data = array(), $dataName = '')
{
parent::__construct($name, $data, $dataName);
if (!static::$kernel) {
static::$kernel = self::createKernel(array(
'environment' => $this->environment,
'debug' => $this->debug
));
static::$kernel->boot();
}
$this->container = static::$kernel->getContainer();
$this->em = $this->getEntityManager();
$this->dir = __DIR__;
}
/**
* Executes fixtures
*
* #param \Doctrine\Common\DataFixtures\Loader $loader
*/
protected function executeFixtures(Loader $loader)
{
$purger = new ORMPurger();
$executor = new ORMExecutor($this->em, $purger);
$executor->execute($loader->getFixtures());
}
/**
* Load and execute fixtures from a directory
*
* #param string $directory
*/
protected function loadFixturesFromDirectory($directory)
{
$loader = new Loader();
$loader->loadFromDirectory($directory);
$this->executeFixtures($loader);
}
/**
* Returns the doctrine orm entity manager
*
* #return object
*/
protected function getEntityManager()
{
return $this->container->get($this->entityManagerServiceId);
}
}
Solution:
You'll have to pass the container to your fixture-loader inside the TestCase yourself.
Just use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader ...
... which extends Doctrine\Common\DataFixtures\Loader and expects the container ...
... as constructor argument.
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
protected function loadFixturesFromDirectory($directory)
{
$loader = new ContainerAwareLoader($this->container);
$loader->loadFromDirectory($directory);
$this->executeFixtures($loader);
}
When does symfony2 inject the container automatically?
The FrameworkBundle injects the container into instances of ContainerAwareInterface being controllers or commands only.
The corresponding code can be found here and here.
DataFixtures can be container-aware, too.
DoctrineFixtureBundle's Command searches for (container-aware) fixtures and injects the container.
Only fixtures that conventially live in a bundle's DataFixtures/ORM folder are processed.
A quick look at the code reveals it:
foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
$paths[] = $bundle->getPath().'/DataFixtures/ORM';
}
The --fixtures flag:
Appending the --fixtures flag - which can be a string or an array - to the doctrine:fixtures:load allows to add additional fixture-paths for processing.