How can I test a method being called once - laravel

Basically I want to test that when I call a method 2 times another method is called once but I get the following Exception:
Mockery\Exception\BadMethodCallException : Received
Mockery_0_App_Repository_DimensionRepository::getThinClientDimension(),
but no expectations were specified
My test is as follows
class HostRepositoryTest extends TestCase
{
/**
* #var HostRepository
*/
private $hostRepository;
/**
* #var Dimension
*/
private $dimension;
public function setUp(): void
{
parent::setUp();
$this->dimension = new Dimension();
$mockDimensionRepository = Mockery::mock(DimensionRepository::class);
$mockDimensionRepository->shouldReceive('getDimensionByName')
->once()
->andReturn($this->dimension);
$this->hostRepository = new HostRepository($mockDimensionRepository);
}
/**
* Test lazy loading dimension retrieval
*/
public function testGetThinClientDimension()
{
$this->hostRepository->getEnvironmentsHostList([]);
$this->hostRepository->getEnvironmentsHostList([]);
}
}
HostRepository:
[...]
/**
* #param $configurationIds
* #return Host[]|Collection
*/
public function getEnvironmentsHostList($configurationIds)
{
//dd('test'); If I uncomment this it will be executed in the test
$hostDimension = $this->dimensionRepository->getThinClientDimension();
dd($hostDimension); // This is not executed if the test is ran
//Returns an entity through Eloquent ORM
[...]
}
DimensionRepositoy:
class DimensionRepository
{
private $thinClientDimension;
const THINCLIENT = 'ThinclientId';
[...]
public function getDimensionByName($name)
{
return Dimension::where(['Name' => $name])->firstOrFail();
}
/**
* Lazy load Thinclient dimension
* #return Dimension
*/
public function getThinClientDimension()
{
dd('test'); // This part is not executed when running the test which I find weird
if ($this->thinClientDimension === NULL) {
$this->thinClientDimension
= $this->getDimensionByName(self::THINCLIENT);
}
return $this->thinClientDimension;
}
[...]
Update:
It seems that when I call $this->dimensionRepository->getThinClientDimension() (in getEnvironmentsHostList) the exception is thrown.
Seems I have to mock this as well (getThinClientDimension) which would make my test useless because as you can see it delegates the call to the mocked method getDimensionByName...

Apparently the fix was to use makePartial() when mocking the object
public function setUp(): void
{
parent::setUp();
$this->dimension = self::getDimension();
$this->mockDimensionRepository = $this->mock(DimensionRepository::class)->makePartial();
$this->hostRepository = new HostRepository($this->mockDimensionRepository);
}
LegacyMockInterface
/**
* Set mock to defer unexpected methods to its parent if possible
*
* #return Mock
*/
public function makePartial();
Seems that this modifier tells Mockery it can call other methods that are not mocked (which should be by default in my opinion)

Related

Spying on class which is in constructor fails to record received method call

What I want to do, is to assert that a class's method was called during a request. The code will probably explain better what I'm trying to do:
Test:
public function use_default_provider_when_getting_addresses()
{
$spy = $this->spy(CraftyClicksService::class);
$this->app->bind(CraftyClicksService::class, function () use ($spy) {
return $spy;
});
$addresses = $this->get('/api/v1/address?postcode=POSTCODE')->decodeResponseJson();
$this->assertTrue(in_array([
'line_1' => 'Line 1',
'line_2' => 'Line 2',
'postcode' => 'POSTCODE',
], $addresses));
$spy->shouldHaveReceived('getAddresses')->once();
}
The request hits a simple controller:
public function show(Request $request, AddressService $addressService)
{
return $addressService->findAddress($request->input('postcode'));
}
The address service class (for now) then calls another class's function to retrieve the addresses.
class AddressService
{
protected CraftyClicksService $craftyClicksService;
/**
* AddressService constructor.
*/
public function __construct()
{
$this->craftyClicksService = new CraftyClicksService();
}
/**
* #param string $postcode
* #return array
*/
public function findAddress(string $postcode)
{
return $this->craftyClicksService->getAddresses($postcode);
}
I can 'spy' on the AddressService class and assert it received the findAddress method, however it seems I cant assert the getAddresses function for the CraftyClicksService class.
I always get a Method getAddresses(<Any Arguments>) from Mockery_3_App_Services_CraftyClicksService should be called at least 1 times but called 0 times. error, even though the response test passes and I can confirm that method is indeed the one called.
Tried to use the $this->app->bind as well as the $this->spy() but test still fails.
You need to use dependency injection and let the service container resolve the dependency from the container so the AddressService class should change to
class AddressService
{
protected CraftyClicksService $craftyClicksService;
/**
* AddressService constructor.
*/
public function __construct(CraftyClicksService $CraftyClicksService)
{
$this->craftyClicksService = $CraftyClicksService;
}
/**
* #param string $postcode
* #return array
*/
public function findAddress(string $postcode)
{
return $this->craftyClicksService->getAddresses($postcode);
}
This way, the spy object which you have bound it to the container will be used.

Laravel mass update , still fire event

As stated in the doc, laravel will not fire an event on mass update/insert/delete.
https://laravel.com/docs/5.8/eloquent#events
It uses the Builder for this and will not fire an event.
Is there a way that I can still fire an event after a mass update for example? I would only need the query Builder to extract the needed info myself ( log purposes).
It is actually possible , but you have to extend the Eloquent builder ,overwrite the update/insert methods and send the event there.
Just been playing around with it... Needs work, but the basic idea is the following :
class Test extends Model
{
protected $guarded = [];
public $dispatchesEvents = [
'saved' => SavedTest::class
];
/**
* INCLUDE this as a trait in your model.
* Overwrite the eloquentBuilder.
*
* #param \Illuminate\Database\Query\Builder $query
* #return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new TestBuilder($query);
}
}
Extend the eloquent builder...
class TestBuilder extends Builder
{
/**
* Update a record in the database and fire event.
*
* #param array $values
* #return int
*/
public function update(array $values)
{
// normal eloquent behavior.
$result =$this->toBase()->update($this->addUpdatedAtColumn($values));
/*
* Fire event.
*/
if($result){
if( $event = Arr::get($this->model->dispatchesEvents,'saved')){
// at the attributes.
$this->model->fill($this->addUpdatedAtColumn($values));
$queryBuilder =$this->toBase();
event(new $event($this->model,$queryBuilder));
}
}
}
public function insert(array $values)
{
// same idea..
}
}
The event class :
class SavedTest
{
use SerializesModels;
public $model;
public $query;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($model,$query =null)
{
$this->model = $model;
$this->query = $query;
}
}
The listener.
class SavedTestEvent
{
/**
* Create the event listener.
*
*
*/
public function __construct()
{
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle($event)
{
// The model , with the attributes.
dump($event->model);
// the query builder , you could extract the wheres or whatever to build your own log for it.
dump($event->query);
}
}
#Paolo on batch request it would not be file the event you must have to perform operation on single record.. like
Analytic::where('id', '>', 100)->get()->each(function($analytic) {
$analytic->delete();
});

How to pass function name to model event callback in Laravel 5

I'd like to hook a model event to perform a task after the model has been deleted. I've added the following code to my model:
protected static function boot()
{
parent::boot();
static::deleted( 'static::removeStorageAllocation' );
}
Rather than put the logic I want to run inside a closure in the boot function, which seems a pretty ugly spot for it, I noticed in the method signature it supposedly takes "\Closure|string $callback" is there a way I can specify a function name like I've tried to do above? I can't seem to come up with anything that works. I've tried lots of combinations:
'self::removeStorageAllocation'
'static::removeStorageAllocation'
'\App\MyModel::removeStorageAllocation'
I know I can probably just specify a closure which calls my function, but I'm wondering what the string form of $callback is for?
You could just pass an anonymous function:
static::deleted(function() {
static::removeStorageAllocation();
});
To know the string representation of $callback, you could look at the source of deleted:
/**
* Register a deleted model event with the dispatcher.
*
* #param \Closure|string $callback
* #param int $priority
* #return void
*/
public static function deleted($callback, $priority = 0)
{
static::registerModelEvent('deleted', $callback, $priority);
}
You'll see it is registering an event listener:
/**
* Register a model event with the dispatcher.
*
* #param string $event
* #param \Closure|string $callback
* #param int $priority
* #return void
*/
protected static function registerModelEvent($event, $callback, $priority = 0)
{
if (isset(static::$dispatcher))
{
$name = get_called_class();
static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback, $priority);
}
}
Therefore, $callback is used eventually as a listener. A string representation would most likely be the name of a listener class, not a method.
Create a protected or public static function on your model (private will not work):
protected static function myStaticCallback($model)
{
// Your code
}
Then add a boot method to your model, using an array for the callback [class, function]:
protected static function boot()
{
parent::boot();
static::creating(['MyModel', 'myStaticCallback']);
}

Testing email from shell Cakephp 3.x

I want to make test cases, with phpunit and cakephp 3.x, shell that send email. This is my function into shell:
class CompaniesShellTest extends TestCase
{
public function monthlySubscription()
{
/* .... */
$email = new Email('staff');
try {
$email->template('Companies.alert_renew_success', 'base')
->theme('Backend')
->emailFormat('html')
->profile(['ElasticMail' => ['channel' => ['alert_renew_success']]])
->to($user->username)
//->to('dario#example.com')
->subject('Eseguito rinnovo mensile abbonamento')
->viewVars(['company' => $company, 'user' => $user])
->send();
} catch (Exception $e) {
debug($e);
}
/* ... */
}
}
In my testing class i have this functions
/**
* setUp method
*
* #return void
*/
public function setUp()
{
parent::setUp();
$this->io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
$this->CompaniesShell = new CompaniesShell($this->io);
}
/**
* tearDown method
*
* #return void
*/
public function tearDown()
{
unset($this->CompaniesShell);
parent::tearDown();
}
/**
* Test monthlySubscription method
*
* #return void
*/
public function testMonthlySubscription()
{
$email = $this->getMock('Cake\Mailer\Email', array('subject', 'from', 'to', 'send'));
$email->expects($this->exactly(3))->method('send')->will($this->returnValue(true));
$this->CompaniesShell->MonthlySubscription();
}
But this doesn't work.
Any ideas? I want to check if the mails are successfully sent and how many times.
The way you wrote your code won't work.
$email = new Email('staff');
And:
$email = $this->getMock('Cake\Mailer\Email', array('subject', 'from', 'to', 'send'));
How do you expect the class you call to magically replace the $email variable with your mock object? You'll need to refactor your code.
This is how I would do it:
First implement a custom mailer like SubscriptionMailer. Put your mail code into this mailer class. That makes sure you have nice separated and reuseable code.
public function getMailer() {
return new SubscriptionMailer();
}
In your test mock the getMailer() method of your shell and return your email mock.
$mockShell->expects($this->any())
->method('getMailer')
->will($this->returnValue($mailerMock));
You can then do the expectation you already have.
$email->expects($this->exactly(3))->method('send')->will($this->returnValue(true));
Also depending on what your shell method is doing, maybe it is better to send the email in the afterSave callback (again using the custom mailer class) of a model object (table) that is processing the data from your shell. Check the example at the end of this page.

Laravel 4 - Extend Pagination Class

Is there a way to extend the Pagination Class of Laravel 4 ?
I tried some things but nothing good...
I'm here :
PaginationServiceProvider.php
class PaginationServiceProvider extends \Illuminate\Pagination\PaginationServiceProvider {
/**
* Indicates if loading of the provider is deferred.
* #var bool
*/
protected $defer = false;
/**
* Bootstrap the application events.
* #return void
*/
public function boot(){
$this->package('thujohn/pagination');
}
/**
* Register the service provider.
* #return void
*/
public function register(){
$this->app['paginator'] = $this->app->share(function($app){
$paginator = new Environment($app['request'], $app['view'], $app['translator']);
$paginator->setViewName($app['config']['view.pagination']);
return $paginator;
});
}
/**
* Get the services provided by the provider.
* #return array
*/
public function provides(){
return array();
}
}
Environment.php
class Environment extends \Illuminate\Pagination\Environment {
public function hello(){
return 'hello';
}
}
I replaced 'Illuminate\Pagination\PaginationServiceProvider', by 'Thujohn\Pagination\PaginationServiceProvider',
When I call $test->links() it's ok
When I call $test->hello() it fails
When I call Paginator::hello() it's ok
Any idea ?
Everyting is fine except that Paginator::make() returns Paginator instance, not Environment.
You should move Your method to Paginator class.
Today I've posted on GH my extension for Paginator. You can check it as a reference desmart/pagination

Resources