Functional Testing Events and Subscribers in Symfony 4 - php

I need to functionally test the a subscriber in Symfony 4 and I'm having problems finding how. The Subscriber has the following structure
/**
* Class ItemSubscriber
*/
class ItemSubscriber implements EventSubscriberInterface
{
/**
* #var CommandBus
*/
protected $commandBus;
/**
* Subscriber constructor.
*
* #param CommandBus $commandBus
*/
public function __construct(CommandBus $commandBus)
{
$this->commandBus = $commandBus;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
CommandFailedEvent::NAME => 'onCommandFailedEvent',
];
}
/**
* #param CommandFailedEvent $event
*
* #throws Exception
*/
public function onCommandFailedEvent(CommandFailedEvent $event)
{
$item = $event->getItem();
$this->processFailed($item);
}
/**
* Sends message
*
* #param array $item
*
* #throws Exception
*/
private function processFailed(array $item)
{
$this->commandBus->handle(new UpdateCommand($item));
}
}
The flow of the subscriber is receiving an internal event and send a message by rabbit through the command bus to another project.
How can I test that dispatching the event CommandFailedEvent the line in processFailed(array $item) is executed?
Does anyone has documentation on best practices to test Events and Subscribers in Symfony 4?

If you want to test the process of a command bus handler being call you could test dependency method calls thanks to mock expects. You have some examples in the PHPUnit documentation.
For instance, you would have something like:
$commandBus = $this->getMockBuilder(CommandBus::class)->disableOriginalConstructor()->getMock();
$commandBus->expects($this->once())->method('handle');
// Create your System Under Test
$SUT = new CommandFailedSubscriber($commandBus);
// Create event
$item = $this->getMockBuilder(YourItem::class)->getMock();
$event = new CommandFailedEvent($item);
// Dispatch your event
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber($SUT);
$dispatcher->dispatch($event);
I hope this would be enough for you to explore possibilities and to have the coverage needed for your feature.
Have a nice time testing!

Related

Run jobs one after another in Laravel 8

I'm trying to automate a process composed from multiple jobs. Every job had a goal, so I'm running a global job who is responsible for creating child jobs.
- Job-1
Global Job -> - Job-2
- Job-3
- Job-4
I want to run them one after another, for know the queue process run all the four jobs in the same time and this is what I don't want.
Is it about configration ? otherwise what it the prefect way to handle this kind of needs
This is what y Global job class looks like
<?php
namespace App\Jobs;
use App\Models\Objects;
use App\Helpers\UtilsHelper;
use Illuminate\Support\Facades\Log;
use App\Services\JobsLimitControler;
/**
* This job will have the responsability to create other ones
*/
class AutoImport extends Job
{
/**
* Job key
* #var string $key
*/
public $key;
/**
* Job type
* #var string $jobType
*/
public $type = 'auto-import';
/**
* Job type
* #var string $jobType
*/
public $title = 'Automatic import';
/**
* Job params array
* #var array $params
*/
public $params;
/**
* Location
* #var Objects $object
*/
public Locations $object;
/**
* The number of seconds after which the job's unique lock will be released.
*
* #var int
*/
public $uniqueFor = 600;
/**
* The unique ID of the job.
*
* #return string
*/
public function uniqueId()
{
return $this->key;
}
/**
* Create a new job instance.
* #param string $id Job id
* #param Objects $object
*/
public function __construct(string $id,Objects $object)
{
# Job key attribute
$this->key = $id;
# Preparing job to be trackable
$this->prepareStatus(['key' => $id]);
# Location instance
$this->object = $object;
}
/**
* Execute the job
*/
public function handle()
{
try{
# First we verify if the object is well setted
if($this->object){
$this->callJobOne();
$this->callJobTwo();
$this->callJobThree();
$this->callJobFour();
}else{
Log::channel('auto-import')->info("some log");
}
}catch(\Exception $e){
# Mark the job as failed
$this->fail($e);
# We throw the error to be intercepted by the job tracker
throw $e;
}
}
}
You can use Laravel's Job Batches for grouping purpose.
https://laravel.com/docs/8.x/queues#job-batching
And Job Chaining to progress sequentially.
https://laravel.com/docs/8.x/queues#job-chaining
You may define a set of chained jobs within a batch by placing the chained jobs within an array
https://laravel.com/docs/8.x/queues#chains-within-batches

laravel 5.4 incrementing page hits with event and listeners

I have two models with many to many relationship artists and songs.
then there are fields in my artists table called weekhits and week_date,
i want to increment the value of week-hits when ever a specific artist page is visited by the user
so made event listners
class ArtistEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $artist;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(artist $artist)
{
//
$this->artist = $artist;
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
ant this is the listener
class ArtistViewed
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param ArtistEvent $event
* #return void
*/
public function handle(ArtistEvent $event)
{
$event->artist->increment('week_hits');
}
}
and here is where i fired the listener.
public function artist($id,$slug){
$artist = Artist::where('id', $id)->where('slug', $slug)->first();
Event::fire(new ArtistViewed($artist));
return view('front.artist', compact('artist'));
}
but this code isnot incrementing week-hits field in my artists table..
plz help. iam in the middle of learning laravel.
I have already added an answer that suggests using a job for this, but if you want to go with the existing event/listener setup then just fix the call to Event::fire() to actually fire your event, not your listener.
Event::fire(new ArtistEvent($artist))
Also ensure that the event and listener are registered in your EventServiceProvider's $listen array.
\App\Events\ArtistEvent::class => [
\App\Listeners\ArtistViewed::class
]
I think you're confusing events and listeners - you're firing off the listener to Event::fire() and you pass the $artist into the constructor of the listener, but instead try and take it off the $event.
Keep in mind if your queue driver isn't sync then you will actually need to run your queue from the command line, otherwise these jobs will never be run.
Below is an example of how you might do this with a job that you dispatch. You could fire this off by calling dispatch(new RecordArtistView($artist)).
<?php
namespace App\Jobs;
use App\Artist;
use App\Jobs\Job;
use Illuminate\Contracts\Queue\ShouldQueue;
class RecordArtistView extends Job implements ShouldQueue
{
protected $artist;
/**
* Create a new job instance.
*
* #param \App\Artist $artist
* #return void
*/
public function __construct(Artist $artist)
{
$this->artist = $artist;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$this->artist->increment('week_hits');
}
}

UnitTest object mocking or real object

I had a discussion with my Team Lead, regarding UnitTest, the question was,
In UnitTest do we use Object Mocking or use the Real Object?
I was supporting the Object Mocking concept, as we should only input/output data from Objects.
At the end we agreed to use Real object instead of Mocking so the following was my Test
<?php
namespace App\Services\Checkout\Module\PaymentMethodRules;
use App\Library\Payment\Method;
use App\Services\Checkout\Module\PaymentMethodRuleManager;
class AdminRule implements PaymentMethodRule
{
/**
* #var boolean
*/
private $isAdmin;
/**
* #var bool
*/
private $isBankTransferAvailable;
/**
* #param boolean $isAdmin
* #param bool $isBankTransferAvailable
*/
public function __construct($isAdmin, $isBankTransferAvailable)
{
$this->isAdmin = $isAdmin;
$this->isBankTransferAvailable = $isBankTransferAvailable;
}
/**
* #param PaymentMethodRuleManager $paymentMethodRuleManager
*/
public function run(PaymentMethodRuleManager $paymentMethodRuleManager)
{
if ($this->isAdmin) {
$paymentMethodRuleManager->getList()->add([Method::INVOICE]);
}
if ($this->isAdmin && $this->isBankTransferAvailable) {
$paymentMethodRuleManager->getList()->add([Method::BANK_TRANSFER]);
}
}
}
<?php
namespace tests\Services\Checkout\Module;
use App\Library\Payment\Method;
use App\Services\Checkout\Module\PaymentMethodList;
use App\Services\Checkout\Module\PaymentMethodRuleManager;
use App\Services\Checkout\Module\PaymentMethodRules\AdminRule;
class AdminRuleTest extends \PHPUnit_Framework_TestCase
{
const IS_ADMIN = true;
const IS_NOT_ADMIN = false;
const IS_BANK_TRANSFER = true;
const IS_NOT_BANK_TRANSFER = false;
/**
* #test
* #dataProvider runDataProvider
*
* #param bool $isAdmin
* #param bool $isBankTransferAvailable
* #param array $expected
*/
public function runApplies($isAdmin, $isBankTransferAvailable, $expected)
{
$paymentMethodRuleManager = new PaymentMethodRuleManager(
new PaymentMethodList([]),
new PaymentMethodList([])
);
$adminRule = new AdminRule($isAdmin, $isBankTransferAvailable);
$adminRule->run($paymentMethodRuleManager);
$this->assertEquals($expected, $paymentMethodRuleManager->getList()->get());
}
/**
* #return array
*/
public function runDataProvider()
{
return [
[self::IS_ADMIN, self::IS_BANK_TRANSFER, [Method::INVOICE, Method::BANK_TRANSFER]],
[self::IS_ADMIN, self::IS_NOT_BANK_TRANSFER, [Method::INVOICE]],
[self::IS_NOT_ADMIN, self::IS_BANK_TRANSFER, []],
[self::IS_NOT_ADMIN, self::IS_NOT_BANK_TRANSFER, []]
];
}
}
My question is, in Unit Test should is use Real Objects or Object Mocking and why?
Second Question, the given Unit test is right or wrong in terms of Unit testing.
The generic answer to such a generic question is: you prefer to use as much of "real" code as possible when doing unit tests. Real code should be default, mocked code is the exception!
But of course, there are various valid reasons to use mocking:
The "real" code does not work in your test setup.
You want to use your mocking framework also to verify that certain actions took place
Example: the code that you intend to test makes a call to some remote service (maybe a database server). Of course that means that you need some tests that do the end to end testing. But for many tests, it might be much more convenient to not do that remote call; instead you would use mocking here - to avoid the remote database call.
Alternatively, as suggested by John Joseph; you might also start with mocking all/most dependencies; to then gradually replace mocking with real calls. This process can help with staying focused on testing exactly "that part" that you actually want to test (instead of getting lost in figuring why your tests using "real other code" is giving you troubles).
IMHO I think it would be good if the original code could be tested directly without any mocking as this would make it less error-prone, and would avoid the debate that if the mocked object behaves almost the same as the original one, but we are not living in the world of unicorns anymore, and mocking is a necessary evil or it is not? This remains the question.
So I think I can rephrase your question to be when to use dummy, fake, stub, or mock?
Generally, the aforementioned terms are known as Test doubles.
As a start, you can check this answer here
Some of the cases when test doubles might be good:
The object under test/System Under Test (SUT) a lot of dependencies, that are required for initialization purposes, and these dependencies would not affect the test, so these dependencies can be dummy ones.
/**
* #inheritdoc
*/
protected function setUp()
{
$this->servicesManager = new ServicesManager(
$this->getDummyEntity()
// ........
);
}
/**
* #return \PHPUnit_Framework_MockObject_MockObject
*/
private function getDummyEntity()
{
return $this->getMockBuilder(Entity\Entity1::class)
->disableOriginalConstructor()
->setMethods([])
->getMock();
}
SUT has an external dependencies such as an Infrastructure/Resource (e.g. web service, database, cash, file …), then it is a good approach to fake that by using in-memory representation, as one of the reasons to do that is to avoid cluttering this Infrastructure/Resource with test data.
/**
* #var ArrayCollection
*/
private $inMemoryRedisDataStore;
/**
* #var DataStoreInterface
*/
private $fakeDataStore;
/**
* #inheritdoc
*/
protected function setUp()
{
$this->inMemoryRedisDataStore = new Collections\ArrayCollection;
$this->fakeDataStore = $this->getFakeRedisDataStore();
$this->sessionHandler = new SessionHanlder($this->fakeDataStore);
}
/**
* #return \PHPUnit_Framework_MockObject_MockObject
*/
private function getFakeRedisDataStore()
{
$fakeRedis = $this->getMockBuilder(
Infrastructure\Memory\Redis::class
)
->disableOriginalConstructor()
->setMethods(['set', 'get'])
->getMock();
$inMemoryRedisDataStore = $this->inMemoryRedisDataStore;
$fakeRedis->method('set')
->will(
$this->returnCallback(
function($key, $data) use ($inMemoryRedisDataStore) {
$inMemoryRedisDataStore[$key] = $data;
}
)
);
$fakeRedis->method('get')
->will(
$this->returnCallback(
function($key) use ($inMemoryRedisDataStore) {
return $inMemoryRedisDataStore[$key];
}
)
);
}
When there is a need of asserting the state of SUT, then stubs become handy. Usually, this would be confused with a fake object, and to clear this out, fake objects are helping objects and they should never be asserted.
/**
* Interface Provider\SMSProviderInterface
*/
interface SMSProviderInterface
{
public function send();
public function isSent(): bool;
}
/**
* Class SMSProviderStub
*/
class SMSProviderStub implements Provider\SMSProviderInterface
{
/**
* #var bool
*/
private $isSent;
/**
* #inheritdoc
*/
public function send()
{
$this->isSent = true;
}
/**
* #return bool
*/
public function isSent(): bool
{
return $this->isSent;
}
}
/**
* Class PaymentServiceTest
*/
class PaymentServiceTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Service\PaymentService
*/
private $paymentService;
/**
* #var SMSProviderInterface
*/
private $smsProviderStub;
/**
* #inheritdoc
*/
protected function setUp()
{
$this->smsProviderStub = $this->getSMSProviderStub();
$this->paymentService = new Service\PaymentService(
$this->smsProviderStub
);
}
/**
* Checks if the SMS was sent after payment using stub
* (by checking status).
*
* #param float $amount
* #param bool $expected
*
* #dataProvider sMSAfterPaymentDataProvider
*/
public function testShouldSendSMSAfterPayment(float $amount, bool $expected)
{
$this->paymentService->pay($amount);
$this->assertEquals($expected, $this->smsProviderStub->isSent());
}
/**
* #return array
*/
public function sMSAfterPaymentDataProvider(): array
{
return [
'Should return true' => [
'amount' => 28.99,
'expected' => true,
],
];
}
/**
* #return Provider\SMSProviderInterface
*/
private function getSMSProviderStub(): Provider\SMSProviderInterface
{
return new SMSProviderStub();
}
}
If the behavior of SUT should be checked then mocks most probably will come to the rescue or stubs (Test spy), it can be detected as simple as that most probably no assert statements should be found. for example, the mock can be setup to behave like when it get a call to X method with values a, and b return the value Y or expect a method to be called once or N of times, ..etc.
/**
* Interface Provider\SMSProviderInterface
*/
interface SMSProviderInterface
{
public function send();
}
class PaymentServiceTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Service\PaymentService
*/
private $paymentService;
/**
* #inheritdoc
*/
protected function setUp()
{
$this->paymentService = new Service\PaymentService(
$this->getSMSProviderMock()
);
}
/**
* Checks if the SMS was sent after payment using mock
* (by checking behavior).
*
* #param float $amount
*
* #dataProvider sMSAfterPaymentDataProvider
*/
public function testShouldSendSMSAfterPayment(float $amount)
{
$this->paymentService->pay($amount);
}
/**
* #return array
*/
public function sMSAfterPaymentDataProvider(): array
{
return [
'Should check behavior' => [
'amount' => 28.99,
],
];
}
/**
* #return SMSProviderInterface
*/
private function getSMSProviderMock(): SMSProviderInterface
{
$smsProviderMock = $this->getMockBuilder(Provider\SMSProvider::class)
->disableOriginalConstructor()
->setMethods(['send'])
->getMock();
$smsProviderMock->expects($this->once())
->method('send')
->with($this->anything());
}
}
Corner cases
SUT has a lot of dependencies which are dependent on other things, and to avoid this dependency loop as we are only interested in testing some methods, the whole object can be mocked, but with having the ability to forward the calls to the original methods.
$testDouble = $this->getMockBuilder(Entity\Entity1::class)
->disableOriginalConstructor()
->setMethods(null);
As per Ahmed Kamal's answer, it worked as expected.
I tested the below sample.
Foo.php
<?php
class Foo
{
/**
* Tell Foo class Name
* #param string $name
* #return string
*/
public function tellName(string $name = 'Josh'): string
{
return 'Hi ' . $name;
}
}
FooTest.php
<?php
include('Foo.php');
use PHPUnit\Framework\TestCase;
class FooTest extends TestCase
{
/**
* PHPUnit testing with assertEquals
* #return void
*/
public function testTellName()
{
// create the class object
$mockObj = $this->getMockBuilder(Foo::class)
->disableOriginalConstructor()
->setMethods(null)
->getMock();
// get the object function result by passing the method parameter value
// pass different parameter value to get an invalid result
$result = $mockObj->tellName('John');
// validate the result with assertEquals()
$this->assertEquals('Hi John', $result);
}
}
Error and Success results:
Cheers!

How to incorporate dependency injection container into this project

I have tried for days to get this to work.
I am using this framework: https://github.com/DennisSkoko/discord-bot to create a bot. But I wan't to add the DIC container http://container.thephpleague.com/ to this project.
What I want to do is in the Main class is to register service providers (modules/packages/bundles) or whatever you want to call them, just like Laravel etc. does.
So I created a module/ dir and added a serviceprovider class and some other classes as the docs state: http://container.thephpleague.com/service-providers/
First I edited the start.php to inject the container:
use League\Container\Container;
require "vendor/autoload.php";
require "bot.php";
$container = new Container;
$main = new DS\Main(new Example($container));
$main->run();
I changed the Example bot to include a getContainer() function so I could read it from the Main class, like this:
use DS\Bot;
use Discord\Discord;
use DS\Service;
class Example extends Bot
{
/**
* Example constructor
*/
public function __construct($container)
{
$this->config = [
"token" => "My token"
];
$this->container = $container;
}
/**
* Will be executed when the WebSocket is ready and before the services are implemented.
*
* #param Discord $discord
*
* #return void
*/
public function setup(Discord $discord)
{
echo "Example is ready to start!";
}
public function getContainer()
{
return $this->container;
}
/**
* Will return an array of Service that the bot uses.
*
* #return Service[]
*/
public function getServices()
{
return [];
}
}
I then changed the Main class to to register my service provider.
namespace DS;
use Discord\Discord;
/**
* A class that bring bots script into life.
*/
class Main
{
/**
* #var Discord
*/
private $discord;
/**
* #var Bot
*/
private $bot;
/**
* Main constructor.
*
* #param Bot $bot
*/
public function __construct(Bot $bot)
{
$this->bot = $bot;
$this->discord = new Discord($this->bot->getConfig());
$this->discord->on("ready", function ($discord) {
$this->bot->setup($discord);
$this->setServices($this->bot->getServices());
});
$this->container = $this->bot->getContainer();
$this->container->addServiceProvider('Mynamespace\HelloWorld\ServiceProvider');
}
/**
* Will run the bot.
*
* #return void
*/
public function run()
{
$this->discord->run();
}
/**
* Will add the services for the WebSocket.
*
* #param Service[] $services
*
* #return void
*/
private function setServices($services)
{
foreach ($services as $service) {
$this->discord->on($service->getEvent(), $service->getListener());
}
}
}
Here is the problem:
PHP Fatal error: Uncaught exception 'InvalidArgumentException' with message 'A service provider must be a fully qualified class name or instance of (\League\Container\ServiceProvider\ServiceProviderInterface)'
Which is weird because I did it just like in the documentation and extended League\Container\ServiceProvider\AbstractServiceProvider
This I have double checked 20 times.
So I have no idea what to do about this. How can I use addServiceProvider() in the Main class and register things in the DIC?
I do not really want to do it in the Example class if possible. Because that should be extended by the user and the Main class will bootstrap the bot.
I also tried $this->bot->addServiceProvider() but then I get the following error:
PHP Fatal error: Call to undefined method Example::addServiceProvider()
Any help appreciated.

How to use method injection with App::call in Laravel

I am using Laravel 5.2 and wrote my own Service Provider. I want to inject a Request object into the register method.
The base problem is that I want to call different service container depending on a special request param - all the service container implementing the same interface/contract of course.
The error message I am getting is:
ReflectionException in Container.php line 559:
Function registerService() does not exist
My service provider looks like that:
<?php
namespace App\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
use App\Contracts\Extractor;
class ExtractorServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = true;
/**
* Available services for channels
*
* #var array
*/
protected $availableServices = ['AExtractor', 'ZExtractor'];
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->call('registerService');
}
/**
* #param Request $request
* #return void
*/
protected function registerService(Request $request)
{
$tag = DB::table('channels')->where('id', $request->channel)->value('tag')->first()->tag;
$selectedExtractor = $tag . 'Extractor';
$extractor = 'AExtractor';
if(in_array($selectedExtractor, $this->availableServices)) {
$extractor = $selectedExtractor;
}
$this->app->bind('App\Contracts\Extractor', "App\\Helpers\\{$extractor}");
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return [Extractor::class];
}
}
How can I use $this->app->call('registerService'); to call my registerService function and inject the Request object?
The problem is you're calling App:call in a wrong way: you have to specify the object on which you want to call the method and the method, like this :
$this->app->call( [ $this, 'registerService' ] );

Categories