phpSpec no beCalled([array:0]) matcher found for null - laravel-5

I'm trying to get into the workfow of using phpSpec to desing my classes. I have stumbled on testing a handle method on one of my event handlers.
My spec:
use App\Order;
use App\Models\Payments\Payment;
use App\Services\Payments\PaymentService;
use App\Events\Payments\AccountPayment;
class RecordPaymentTransactionSpec extends ObjectBehavior
{
function let(PaymentService $paymentService)
{
$this->beConstructedWith($paymentService);
}
function it_is_initializable()
{
$this->shouldHaveType('App\Handlers\Events\Payments\RecordPaymentTransaction');
}
function it_should_record_payment_transaction(AccountPayment $event)
{
$this->paymentService->recordPayment($event)->shouldBeCalled();
}
}
And this my implementation:
class RecordPaymentTransaction {
public $paymentService;
/**
* Create the event handler.
*
* RecordPaymentTransaction constructor.
* #param PaymentService $paymentService
*/
public function __construct(PaymentService $paymentService)
{
$this->paymentService = $paymentService;
}
/**
* Handle the event.
*
* #param AccountPayment $event
* #return void
*/
public function handle(AccountPayment $event)
{
$this->paymentService->recordPayment($event);
}
}
However, I keep getting this error:
- it should record payment transaction
no beCalled([array:0]) matcher found for null.
Cannot see what I'm doing wrong here, help please.

function it_should_record_payment_transaction(AccountPayment $event)
{
$this->paymentService->recordPayment($event)->shouldBeCalled();
}
should be
function it_should_record_payment_transaction(
PaymentService $paymentService,
AccountPayment $event
) {
$paymentService->recordPayment($event)->shouldBeCalled();
$this->handle($event);
}
or
function it_should_record_payment_transaction(AccountPayment $event)
{
$prophet = new Prophet();
$paymentService = $prophet->prophesize(PaymentService::class);
$paymentService->recordPayment($event)->shouldBeCalled();
$this->handle($event);
$prophet->checkPredictions();
}
This is because, when you spec a class, you should only call a public method of it and make expectations on collaborators.
You don't need to (and you are not supposed to do) call collaborators with
$this->collaboratorName->method->shouldBeCalled()
Use "implicit" mocks (by passing them into spec method signature: if they are used in let function and have same name and type, they are the same mock for phpspec) or "explicit" mocks that you can obtain by calling prophesize on Prophet object. The only difference between them is that "implicit" mocks are checked automatically whereas "explicit" ones need to be checked manually ($prophet->checkPredictions();)

Related

Laravel 8 vendor class `Illuminate\Database\Eloquent\Factories\Factory` can't resolve name of ModelNameFactory class

Laravel 8 has the default App/Models directory for Model classes. The Illuminate\Database\Eloquent\Factories\Factory has static function resolveFactoryName() to resolve name of ModelNameFactory class
public static function resolveFactoryName(string $modelName)
{
$resolver = static::$factoryNameResolver ?: function (string $modelName) {
$modelName = Str::startsWith($modelName, 'App\\Models\\')
? Str::after($modelName, 'App\\Models\\')
: Str::after($modelName, 'App\\');
return static::$namespace.$modelName.'Factory';
};
return $resolver($modelName);
}
The function works properly only for App/ModelName or App/Models/ModelName
if name of Model class, for example, is the Domain/Customers/Models/ModelName, that function doesn't work properly. What is the best way to fix it?
As you can see here, there is a method called guessFactoryNamesUsing which lets you tell Laravel how it should guess the name of your factories.
Add the following to your AppServiceProvider:
use Illuminate\Database\Eloquent\Factories\Factory;
public function register()
{
Factory::guessFactoryNamesUsing(function ($class) {
return 'Database\\Factories\\' . class_basename($class) . 'Factory';
});
}
Source:
/**
* Specify the callback that should be invoked
* to guess factory names based on dynamic relationship names.
*
* #param callable $callback
* #return void
*/
public static function guessFactoryNamesUsing(callable $callback)
{
static::$factoryNameResolver = $callback;
}
Please put this in your model class in App\Models\ModelName.
Make sure the ModelFactory is the factory name.
protected static function newFactory()
{
return \Modules\Module\Database\Factories\ModelFactory::new();
}

How to change the using of the pattern repository loaded using interfaces for initializing the model? Laravel, Laracom

Good day.
A store was created based on https://github.com/Laracommerce/laracom on Laravel.
In the process, it was noticed that, along with pulling up the implementation for the interface with a call like:
use App\Products\Repositories\Interfaces\ProductRepositoryInterface;
the binding of which is declared in RepositoryServiceProvider (app \ Providers \ RepositoryServiceProvider.php),
direct calls like use App\Shop\Products\Repositories\ProductRepository are used;
(e.g. here app/Shop/Orders/Repositories/OrderRepository.php)
You can find several similar examples in the code, and most often a direct address is required to invoke
$repositoryWithModel = new Repository($modelObject).
I did not find a definite way out of this situation, I ask the advice of those who came across an example of quality implementation.
The implementation of your ProductRepository expects a Product as constructor parameter. A respository should not do that. Instead if a repository has to handle a product model, it should be passed as a parameter to a function.
For example this:
/**
* #param Brand $brand
*/
public function saveBrand(Brand $brand)
{
$this->model->brand()->associate($brand);
}
Can be rewritten to:
/**
* #param Product $product
* #param Brand $brand
*/
public function saveBrand(Product $product, Brand $brand)
{
$product->brand()->associate($brand);
}
If you remove the Product parameter from the constructor, then you can use the repository without creating it using the new keyword every time:
class BrandController extends Controller {
public function __construct(ProductRepositoryInterface $repository) {
$this->repository = $repository;
}
public function linkBrandToProduct(Request $request): void {
$product = $this->repository->findProductById($request->productId);
$brand = $this->repository->findBrandById($request->brandId);
$this->repository->saveBrand($product, $brand);
}
}

Is it a good practice to add custom method on Laravel Model class to insert record in another table?

I am following a tutorial to create a referal system in Laravel. In the tutorial it was not shown how to implement the addCredit() method of the user model class. I am a bit confuse. Assuming I have another table to keep the record of credits like :
user_credits
------------
user_id
credits
Is it good practice to write the code on user model's addCredits method to update the user_credits table? What will be the best in this case?
class User extends Authenticatable
{
/**
* Add bonus to the user
*/
public function addCredits($credit) {
//
}
}
The listener class to handle addition of the bonus for both the users.
namespace App\Listeners;
use App\Events\UserReferred;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class RewardUser
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param UserReferred $event
* #return void
*/
public function handle(UserReferred $event)
{
$referral = \App\ReferralLink::find($event->referralId);
if (!is_null($referral)) {
\App\ReferralRelationship::create(['referral_link_id' => $referral->id, 'user_id' => $event->user->id]);
if ($referral->program->name === 'Sign-up Bonus') {
// User who was sharing link
$provider = $referral->user;
// add credits to provider
$provider->addCredits(15);
// User who used the link
$user = $event->user;
$user->addCredits(20);
}
}
}
}
I'm not pretty sure, is it good practice or not, but i prefer abstract such things into a standalone service.
In your case it would be something like that:
CreditService
namespace App\Services;
use App\User;
class CreditService
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function addCredits($credits)
{
$this->user->credits += $credits;
$this->user->save();
}
}
Then in controller/listener you can work with this service
use App\Services\CreditService;
...
public function handle(UserReferred $event)
{
$referral = \App\ReferralLink::find($event->referralId);
if ( !is_null($referral) ) {
\App\ReferralRelationship::create([
'referral_link_id' => $referral->id,
'user_id' => $event->user->id,
]);
if ( $referral->program->name === 'Sign-up Bonus' ) {
(new CreditService($referral->user))->addCredits(15);
(new CreditService($event->user))->addCredits(20);
}
}
}
The way how you make and then use service might be different. So, if you don't want work via constructors, you can write static class and pass User into method directly.
I often put some additional actions into services. For example, fire events when i need to do it. Or log some things.

Laravel - Eloquent override get method when using where

I override eloquent get() method in one of my models OrderStatus
public static function get()
{
return "hit";
}
when I call it without where it's working fine
>>> $order_statuses = OrderStatus::get();
=> "hit"
But when I call it with where it uses the parent get method again:
>>> $order_statuses = OrderStatus::where('order_id', 24)->get();
=> Wilgucki\Csv\CsvCollection {#4434
all: [],
}
Is there a way to override it anyway?
you can do that by overriding the get() method inside the query builder in \Illuminate\Database\Query\Builder, an example of this override is provided in this medium post. But in your case it seems you want to override it only when used against the OrderStatuses model.
The good news is that the Builder class has a reference to the table:
/**
* The table which the query is targeting.
*
* #var string
*/
public $from;
it is set here:
/**
* Set the table which the query is targeting.
*
* #param string $table
* #return $this
*/
public function from($table)
{
$this->from = $table;
return $this;
}
so you can do something like this:
namespace App\Override;
class QueryBuilder extends \Illuminate\Database\Query\Builder {
//#Override
public function get($columns = ['*']) {
if ($this->from == 'OrderStatus') {
// customize the call
} else {
//Return default
return parent::get($columns);
}
}
}
The get() function is not defined on the Model class but it is called as a dynamic method on the Eloquent QueryBuilder, that is is handled by calling on the Model class this function:
public static function __callStatic($method, $parameters)
At the end when you call the get() function on Model you are instead calling it dynamically on the Illuminate\Database\Query\Builder class.
So you can't really override the chainable method get() on a class derived from Model without messing up things.

Can authorize method in Request class return customized message for HandlesAuthorization trait?

I have the following code in Request class to check if the user is authorized to perform update.
HandlesAuthorization trait, by default gives default message. Is there any way to return customized message? I saw the authorize method in Request class can return boolean value only.
class UpdateRoleRequest extends Request
{
private $UserPermissionsSession;
public function __construct(IRole $Role) {
$this->UserPermissionsSession = new UserPermissionsSession();
}
public function authorize() {
$UserID = \Auth::user()->UserID;
return $this->UserPermissionsSession->CheckPermissionExists($UserID);
}
}
I believe you shouldn't look at HandlesAuthorization trait. All you need to do is implementing failedAuthorization method in your request class.
In FormRequest class it's defined like this:
/**
* Handle a failed authorization attempt.
*
* #return void
*
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function failedAuthorization()
{
throw new AuthorizationException('This action is unauthorized.');
}
so all you need is to override this method in your UpdateRoleRequest class for example like this:
protected function failedAuthorization()
{
throw new \Illuminate\Auth\Access\AuthorizationException('User has to be an admin.');
}
To provide a solution answering #Pooria Honarmand's question for anyone else wondering the same;
If you have more specific messages for different conditions that you already checked in the authorize method and you don't want to repeat those checks here, just introduce one or more class-based variables.
Here is one example having only one condition which does result in a non-standard message:
private bool $hasMissingClientId = false;
public function authorize(): bool
{
// several other checks
if (empty($user->client_id)) {
$this->hasMissingClientId = true;
return false;
}
return true;
}
protected function failedAuthorization()
{
if ($this->hasMissingClientId) {
throw new AuthorizationException('User has to be assigned to specific client.');
}
parent::failedAuthorization();
}

Resources