How to test data using PHP unit and Symfony Command - php

I'm trying to write a test class that tests when data is updated using the command in Symfony. But I don't know how to write a functional test. I need to know, which functions to write in the class UpdateTrackedContainerCommandTest, so that data was updated. In the documentation from Symfony I have not found any examples of it. Please, is there someone, who can give me some advice? I much appreciate any help.
Here is a command class in Symfony, that updates data through the command console:
#[AsCommand(
name: 'app:update-data',
description: 'Updating data',
hidden: false
)]
class UpdateDataCommand extends Command
{
private $repository;
private $apiService;
private $dbManager;
public function __construct(
Repository $repository,
ApiService $apiService,
DbManager $dbManager
) {
$this->repository = $repository;
$this->apiService = $apiService;
$this->dbManager = $dbManager;
parent::__construct();
}
protected function configure(): void
{
$this
->setHelp('This command allows you to update data...')
->setDescription('Updating data');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$repositoryData = $this->repository->findAll();
$isUpdatedData = false;
foreach ($repositoryData as $dataRow) {
$apiData = $this->apiService->getApiData($dataRow->getReference())['result'] ?? null;
if ($apiData) {
foreach ($apiData['result'] as $key => $row) {
if ($row['id'] == $dataRow['id']) {
$dataRow[$key] = $row[$key];
$this->dbManager->persist($dataRow);
$isUpdatedData = true;
}
}
}
}
if ($isUpdatedData) {
$this->dbManager->flush();
}
return Command::SUCCESS;
}
}
Here is a test class in Symfony, that tests with PHP unit, that data was updated as expected:
class UpdateTrackedContainerCommandTest extends KernelTestCase
{
public function testExecute(): CommandTester
{
$kernel = self::bootKernel();
$application = new Application($kernel);
$command = $application->find('app:update-tracked-container');
$commandTester = new CommandTester($command);
$commandTester->execute([]);
$commandTester->assertCommandIsSuccessful();
return $commandTester;
}
}

Related

Codecoverage phpunit test issue

I am running phpunit version 9.2 and I would like to know why my method is not covered in the phpunit coverage.
This is my class:
class Methods extends Template
{
const DISABLED_PAYMENT_METHODS_1 = 'free';
const DISABLED_PAYMENT_METHODS_2 = 'adyen_cc';
const DISABLED_PAYMENT_METHODS_3 = 'adyen_oneclick';
protected $paymentMethodList;
protected $storeManager;
protected $logger;
public function __construct(
Context $context,
PaymentMethodList $paymentMethodList,
StoreManagerInterface $storeManager,
LoggerInterface $logger,
array $data = []
) {
$this->paymentMethodList = $paymentMethodList;
$this->storeManager = $storeManager;
$this->logger = $logger;
parent::__construct($context, $data);
}
public function getPaymentMethods()
{
try {
$storeId = $this->storeManager->getStore()->getId();
$paymentList = $this->paymentMethodList->getActiveList($storeId);
$resultPayments = [];
foreach ($paymentList as $payment) {
if ($payment->getCode() !== self::DISABLED_PAYMENT_METHODS_1 &&
$payment->getCode() !== self::DISABLED_PAYMENT_METHODS_2 &&
$payment->getCode() !== self::DISABLED_PAYMENT_METHODS_3
) {
$resultPayments[] = $payment;
}
}
return $resultPayments;
} catch (Exception $e) {
$this->logger->error($e->getMessage());
return false;
}
}
}
and this is my test class:
class MethodsTest extends TestCase
{
private $model;
private function getSimpleMock($originalClassName)
{
return $this->getMockBuilder($originalClassName)
->disableOriginalConstructor()
->getMock();
}
public function setUp() : void
{
$context = $this->getSimpleMock(Context::class);
$paymentMethodList = $this->getSimpleMock(PaymentMethodList::class);
$storeManager = $this->getSimpleMock(StoreManagerInterface::class);
$logger = $this->getSimpleMock(LoggerInterface::class);
$this->model = new Methods(
$context,
$paymentMethodList,
$storeManager,
$logger,
[]
);
}
public function testGetPaymentMethods()
{
$stub = $this->createMock(Methods::class);
$stub->method('getPaymentMethods')
->willReturn([]);
try {
$stub->getPaymentMethods();
$this->fail("Expected exception!");
} catch (\Exception $error) {
$this->assertEquals("Expected exception!", $error->getMessage());
}
}
}
When I run the command to get the coverage. I am getting:
I am really curious why my test is not covered or at least the exception part ? Would you please share you ideas why ? and what can i do in order to fix this ? Right now I got a 29 % and I would like to get at least 60% coverage.
Thank you
On this line $stub = $this->createMock(Methods::class); you are creating a mock of the Methods class, so not actually testing the real class.
You will need to use the object you created in your setUp() method, and set up mock returns on the dependencies you passed in (perhaps converting some of them to be class properties).
You should test the real class, as example:
public function testGetPaymentMethods()
{
// define a payment
$paymentFreeCode = $this->createMock(Payment::class);
$paymentFreeCode->method('getcode')
->willReturn("free");
// define a payment
$payment = $this->createMock(Payment::class);
$payment->method('getcode')
->willReturn("invalid-code");
$paymentList = [
$paymentFreeCode,
$payment,
];
// define a store
$store = $this->createMock(Store::class);
$store->method('getId')
->willReturn("my-store-id");
// return store from the store manager
$this->storeManager->method('getStore')
->willReturn(myStore);
// return the payment list
$this->paymentMethodList->method('getActiveList')->with("my-store-id")
->willReturn($paymentList);
// call the real class instrumented with mocks
$paymentMethods = $this->model->getPaymentMethods();
$this->assertIsArray($paymentMethods);
$this->assertCount($paymentMethods, 1);
}

PHPUnit - Mockery::mock vs Mockery::namedMocks

I'm writing PHPUnit Test with Mockery, (PHP v5.6.32, PHPUnit 3.7.21, Mockery dev-master) and found something which I can't understand about using Mockery::mock and Mockery::namedMocks.
My code is below, and the questions are:
Am I correct to use in LegendTest.php the Mockery::namedMock() instead of Mockery::mock() for SignalsCollection object?
Regarding to documentation about namedMock, I expect that frist argument is the Class name (SignalsCollection) and the second argument should be the extends statement (\ArrayObject) - but in my case I'm getting an error: Mockery\Exception\BadMethodCallException : Received Charts\SignalsCollection::getIterator(), but no expectations were specified, so I'm giving only one argument and this works fine. Why? What am I doing wrong? I'm confused.
Did I missed something in this test case or should I do something different to make tests better?
Signal.php:
class Signal
{
protected $id = 0;
protected $colName = '';
protected $tableName = '';
public function getId()
{
return $this->id;
}
public function setColName($colName)
{
$this->colName = $colName;
return $this;
}
public function setTableName($tableName)
{
$this->tableName = $tableName;
return $this;
}
}
SignalsCollection.php:
class SignalsCollection extends \ArrayObject
{
}
Legend.php
class Legend
{
protected $signalsCollection = null;
protected $graphModel = null;
public function __construct(SignalsCollection $signalsCollection, GraphModel $graphModel)
{
$this->signalsCollection = $signalsCollection;
$this->graphModel = $graphModel;
}
public function getSignalsCollection()
{
return $this->signalsCollection;
}
public function removeSignal(Signal $signal)
{
foreach ($this->signalsCollection as $key => $item) {
if ($item->getId() === $signal->getId()) {
$this->signalsCollection->offsetUnset($key);
break;
}
}
}
}
LegendTest.php:
class LegendTest extends \PHPUnit_Framework_TestCase
{
protected function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testRemoveSignal()
{
$testSignal = Mockery::mock('\Charts\Signal')
->shouldReceive('setColName', 'setTableName')
->andReturn(Mockery::self())
->mock();
$testSignal
->setColName('testColumnName')
->setTableName('testTableName');
$testSignalSecond = Mockery::mock('\Charts\Signal')
->shouldReceive('setId', 'setColName', 'setTableName')
->andReturn(Mockery::self())
->mock();
$testSignalSecond
->setId(1)
->setColName('testColumnName')
->setTableName('testTableName');
$signalsCollection = Mockery::namedMock('\Charts\SignalsCollection')
->shouldReceive('append', 'offsetUnset')
->andReturn(Mockery::self())
->mock();
$signalsCollection
->append($testSignal)
->append($testSignalSecond);
$legend = new Legend($signalsCollection, Mockery::mock('\Charts\GraphModel'));
$this->assertEquals($signalsCollection, $legend->getSignalsCollection());
$legend->removeSignal($testSignalSecond);
$signalsCollection->offsetUnset(1);
$this->assertEquals( $signalsCollection, $legend->getSignalsCollection() );
}
}

Doctrine Entity Manager with PThreads and Symfony 4

I'm using php-zts to perform parallel data processing, using symfony 4 and PThreads
I'm great at running multiple threads, but I'm facing a problem, I need each of the threads to be able to work with doctrine
I need to make sure that each thread is able to work with doctrine
I tried to transfer a container instance directly, but it won't work because it can't be sterilized
/console_comand.php
private function gettingStatistics(){
$pool = new \Pool(4, Autoloader::class, ["vendor/autoload.php"]);
$store = new \Threaded();
$class = new Meta();
$pool->submit(new Task($class,$store));
$pool->collect();
$pool->shutdown();
$listQuotes = array();
foreach ($store as $obj){
foreach ($obj->{'response'} as $exchange => $data){
$listQuotes[$exchange] = $data;
}
}
unset($store);
unset($interface);
return $listQuotes;
}
/Autoloader.php
<?php
namespace App\Worker;
class Autoloader extends \Worker
{
protected $loader;
public function __construct($loader)
{
$this->loader = $loader;
}
/* включить автозагрузчик для задач */
public function run()
{
require_once($this->loader);
}
/* переопределить поведение наследования по умолчанию для нового потокового контекста */
public function start(int $options = PTHREADS_INHERIT_ALL)
{
return parent::start(PTHREADS_INHERIT_NONE);
}
}
/Autoloadable.php
<?php
namespace App\Worker;
/* нормальный, автоматически загруженный класс */
class Autoloadable
{
public $response;
public function __construct($greeting)
{
$this->response = $greeting->job();
}
}
/Task.php
<?php
namespace App\Worker;
class Task extends \Threaded
{
protected $greeting;
protected $result;
public function __construct($greeting,\Threaded $store)
{
$this->greeting = $greeting;
$this->result = $store;
}
public function run()
{
$greeting = new Autoloadable($this->greeting);
$this->result[] = $greeting;
}
}
how do I pass the right doctrine to be able to work with it from the job?
there's a very similar question on github but I can't deal with it.
https://github.com/krakjoe/pthreads/issues/369
Have you tried requiring an ObjectManager instance in the __construct of Task (your last code block)?
Have a read of this article
Cannot test it atm, don't have zts setup, but I've used this to great success in other projects.
I would expect you need to do something like:
$pool = new Pool(4);
for ($i = 0; $i < 15; ++$i) {
$pool->submit(new class($objectManager) extends \Threaded
{
private $objectManager;
public function __construct(ObjectManager $objectManager)
{
$this->objectManager= $objectManager;
}
public function run()
{
// obviously replace the contents of this function
$this->objectManager->performTask;
echo 'Job\'s done.' . PHP_EOL;
}
});
}
while ($pool->collect());
$pool->shutdown();
The instantiation of the new anonymous class takes the $objectManager present in your current instance, like /console_comand.php there, and passes it to this new anonymous class to fulfill the __construct requirements.
The linked article does a better job of explaining it than I do, so please give it a read.

DDD in PHP -> DomainEventPublisher -> Where to use the subscribe method?

The flow:
CreateNewTaskRequest -> CreateNewTaskService -> Task::writeFromNew() -> NewTaskWasCreated(domain event) -> DomainEventPublisher calls handle on subscribers.
Following the flow above, I'm wondering where do you add subscribers for domain events?
I'm currently reading the book DDD in PHP, but I'm unable to grasp where this should be done?
This is the code I have but feels wrong to me
public static function writeNewFrom($title)
{
$taskId = new TaskId(1);
$task = new static($taskId, new TaskTitle($title));
DomainEventPublisher::instance()->subscribe(new MyEventSubscriber());
$task->recordApplyAndPublishThat(
new TaskWasCreated($taskId, new TaskTitle($title))
);
return $task;
}
Task extends Aggregate root:
class AggregateRoot
{
private $recordedEvents = [];
protected function recordApplyAndPublishThat(DomainEvent $domainEvent)
{
$this->recordThat($domainEvent);
$this->applyThat($domainEvent);
$this->publishThat($domainEvent);
}
protected function recordThat(DomainEvent $domainEvent)
{
$this->recordedEvents[] = $domainEvent;
}
protected function applyThat(DomainEvent $domainEvent)
{
$modifier = 'apply' . $this->getClassName($domainEvent);
$this->$modifier($domainEvent);
}
protected function publishThat(DomainEvent $domainEvent)
{
DomainEventPublisher::instance()->publish($domainEvent);
}
private function getClassName($class)
{
$class = get_class($class);
$class = explode('\\', $class);
$class = end($class);
return $class;
}
public function recordedEvents()
{
return $this->recordedEvents;
}
public function clearEvents()
{
$this->recordedEvents = [];
}
}
The DomainEventPublisher class is a singleton, and you can add a subscriber with
DomainEventPublisher::instance()->subscribe(new YourSubscriber());
where YourSubscriber implements DomainEventSubscriber.

Zend Framework 2 how to test forward in controller using phpunit?

How can I test a forward in a controller with PHPUnit?
I have two simple modules (A and B), module A call the module B using a forward.
here is a simple code that not work :
ModuleA
class ModuleAController extends AbstractRestfulController
{
protected $em;
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em
$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
return $this->em;
}
public function getList()
{
$data = array('message' => 'passed by module A');
$forward = $this->forward()->dispatch('ModuleB\Controller\ModuleB');
$data['Message'] = $forward->getVariable('Message');
return new JsonModel($data);
}
}
ModuleB
class ModuleBController extends AbstractRestfulController
{
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
}
public function getList()
{
$data = array('Message'=>'passed by module B');
return new JsonModel($data);
}
}
And this is a test code :
class ModuleAControllerTest extends AbstractHttpControllerTestCase
{
protected $controller;
protected $request;
protected $response;
protected $routeMatch;
protected $event;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new ModuleAController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array());
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
public function testModuleAControllerCanBeAccessed()
{
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, 1+99+100);
}
}
And this is the error message :
There was 1 error:
1) ModuleATest\Controller\ModuleAControllerTest::testModuleAControllerCanBeAccessed
Zend\ServiceManager\Exception\ServiceNotCreatedException: An exception was raised while creating "forward"; no instance returned
....
Caused by
Zend\ServiceManager\Exception\ServiceNotCreatedException: Zend\Mvc\Controller\Plugin\Service\ForwardFactory requires that the application service manager has been injected; none found
....
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
Is there any way to make this code work ??Any idea ??
Thank you.
I have not created mock for plugins yet. I don't know how set new plugin to controller. But mock will be like it.
PHPunit test file
public function testControllerWithMock()
{
/* Result from ModuleBController method getList() */
$forwardResult = new JsonModel(array('Message'=>'passed by module B'));
/* Create mock object for forward plugin */
$forwardPluginMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\Forward')
->disableOriginalConstructor()
->getMock();
$forwardPluginMock->expects($this->once())
->method('dispatch') /* Replace method dispatch in forward plugin */
->will($this->returnValue($forwardResult)); /* Dispatch method will return $forwardResult */
/* Need register new plugin (made mock object) */
$controller->setPluginManager(); /* ??? Set new plugin to controller */
I'm thinking how decide it.
Ok, try it.
$controller->getPluginManager()->injectController($forwardPluginMock);
I don't write PHPUnit tests for controllers. Because controllers must return a view and best solution using Selenium for testing view. I usually use PHPUnitSelenium tests for testing it.

Categories