Hi Latest question on unraveling code - I'm trying to create tests that mock certain classes but struggling to get my first attempt to work. It does not appear to be using the mock in the test but the real class.
There are three questions:
What do I need to change to get this to work and the mock to be used?
Does the mock work for the whole app under test or just the class under test?
If I wanted to return an array of values do I just create the array with the expected values to enable the class under test to use that returned data?
So here's the class I want to mock:
<?php
namespace App\Wedleague\Utility;
use App\Libraries\Utilities\FolderUtility;
use App\Wedleague\LeagueTable\LeagueTableInterface;
use PDF;
/**
* Class PDFGenerator
* Generates a PDF based on a league
* #package App\Libraries\Helpers
*/
class PDFGenerator
{
use FolderUtility;
/**
* set the folder location for the generated pdf
* #var string
*/
private string $storageFolder = 'pdf';
private string $path;
private LeagueTableInterface $leagueTable;
/**
* PDFGenerator constructor.
*/
public function __construct(LeagueTableInterface $leagueTable)
{
$this->path = storage_path('app/' . $this->storageFolder);
$this->setUp();
$this->leagueTable = $leagueTable;
}
/**
* #return \Barryvdh\DomPDF\PDF
*/
public function createLeaguePDF()
{
$this->leagueTable->getLeagueTable();
error_reporting(E_ALL ^ E_DEPRECATED);
return PDF::loadView($this->leagueTable->getViewPath(), $this->leagueTable->getViewData())
->setOptions(['fontSubsetting' => true])
->setPaper('a4', 'landscape')
->save(storage_path('app/pdf/' . $this->leagueTable->getLeagueType() . '_league_update.pdf'));
}
private function setUp()
{
$this->checkFolderExists($this->path);
}
}
Here's the class I'm trying to test that uses this class:
<?php
namespace App\Jobs;
use App\Wedleague\Utility\PDFGenerator;
use App\Mail\MailEclecticUpdate;
use App\Models\League;
use App\Wedleague\LeagueTable\EclecticLeagueTable;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
/**
* Class ProcessSendEclecticUpdate
* #package App\Jobs
*/
class ProcessSendEclecticUpdateEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* #var int
*/
public $tries = 3;
/**
* The number of seconds the job can run before timing out.
*
* #var int
*/
public $timeout = 240;
private League $league;
public function __construct(League $league)
{
$this->league = $league;
}
public function handle()
{
if (!$this->league || $this->league->league_type !== 'eclectic') {
throw new Exception("Error Processing the job - league not found or invalid league", 1);
}
$PDFGenerator = new PDFGenerator(new EclecticLeagueTable($this->league));
$PDFGenerator->createLeaguePDF();
$this->league->player()->get()->each(function ($player) {
if ($player->contactEmail) {
Mail::to($player->contactEmail)
->queue(new MailEclecticUpdate(
$this->league
));
}
});
Log::info('Eclectic League update issued');
}
}
and here's the basics of the test:
<?php
namespace Tests\Jobs;
use App\Jobs\ProcessSendEclecticUpdateEmail;
use App\Mail\MailEclecticUpdate;
use App\Models\League;
use App\Models\User;
use App\Wedleague\Utility\PDFGenerator;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
/**
* Class ProcessSendEclecticUpdateTest
* #package Tests\Jobs
*/
class ProcessSendEclecticUpdateTest extends TestCase
{
use RefreshDatabase;
use WithFaker;
private $user;
private $adminUser;
protected function setUp(): void
{
parent::setUp();
$this->seed();
$this->adminUser = User::factory()->create(['admin' => 1]);
}
/**
* #test
* #covers ProcessSendEclecticUpdateEmail::handle
* #description:
*/
public function testHandle()
{
$mock = $this->mock(\App\Wedleague\Utility\PDFGenerator::class);
$mock->shouldReceive('createLeaguePDF')->once();
$this->app->instance(PDFGenerator::class, $mock);
Mail::fake();
$this->withoutExceptionHandling();
$league = League::find(27);
$players = $league->player()
->get()
->filter(function ($player){
return $player->contactEmail;
});
ProcessSendEclecticUpdateEmail::dispatch($league);
Mail::assertQueued(MailEclecticUpdate::class, $players->count());
}
}
The response I get from the test is:
Mockery\Exception\InvalidCountException : Method createLeaguePDF(<Any Arguments>) from Mockery_2_App_Wedleague_Utility_PDFGenerator should be called
exactly 1 times but called 0 times.
I know this is not using the mock due to the length of time the test takes as it produces a PDF
Any ideas how I can get this to work - it's mocking me!!! :)
Related
I have a problem to solve. However, this problem came to me from sentry.io. So I didn't see the problem directly.
The problem is as follows: Address in mailbox given [] does not comply with RFC 2822, 3.6.2.
enter image description here
The problem with sentry.io is in the picture. However, as far as I understand, the problem is not directly caused by this file. In other words, the problem is in sending mail, so since email is used here, it shows the problem here. I have not used services such as laravel mailing before, so I have no idea right now.
I can share with you the necessary codes, files, etc., so that we can better understand the problem.
Send Mail
Mail::to($user->email)->send(new NothingListenedFor3Days($user));
Mail Class
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class NothingListenedFor3Days extends Mailable
{
use Queueable, SerializesModels;
protected $user;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($user)
{
$this->user = $user;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->subject('Your Subject')
->view('Your blade file path')->with('user',$this->user);
}
}
For reference link
#Ali Raza
I made both files as I shared below. Are there any mistakes I've made?
EmailCronJobController.php
Mail::to($user->email)->send(new NothingListenedFor3Days($user));
NothingListenedFor3Days.php
namespace App\Mail\Listener;
use App\Course;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class NothingListenedFor3Days extends Mailable
{
use Queueable, SerializesModels;
public $listener;
public $random_contents;
public $fromEmail = 'listener#omnicourse.io';
public $fromName = 'Omnicourse';
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(User $listener)
{
$this->listener = $listener;
$this->random_contents = Course::all()->random(10);
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from($this->fromEmail,$this->fromName)
->view('email.listener.nothing_listened_for_3_days')->with('user',$this->listener);
->text('email.listener.nothing_listened_for_3_days_plain')
->subject("Let's move ahead together");
}
}
i am using design patterns repository and i wrote every thing coorect but i have an error returns
Target [App\Repository\categoryRepositoryInterface] is not instantiable while building [App\Http\Controllers\CategoryController]. i dont know why although i wrote every thing correct please help
here is my code
my config/app
App\Providers\RepositoryServiceProvider::class,
and my categorycontroller
<?php
namespace App\Http\Controllers;
use App\Repository\categoryRepositoryInterface;
use Illuminate\Http\Request;
class CategoryController extends Controller
{
private categoryRepositoryInterface $categoryRepository;
public function __construct(categoryRepositoryInterface $categoryRepository)
{
$this->categoryRepository = $categoryRepository;
}
and my repositoryinterface
<?php
namespace App\Repository;
use Illuminate\Http\Request;
interface categoryRepositoryInterface
{
public function createCategory(Request $request);
public function validation(Request $request);
}
and my category repository
<?php
namespace App\Repository;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class categoryRepository implements categoryRepositoryInterface{
public function validation(Request $request){
$validation = Validator::make($request->all(),[
'name' => 'required',
]);
if($validation->fails()){
return response()->json([
'status' => 400,
'errors' => $validation->errors(),
]);
}
}
public function createCategory(Request $request)
{
$category = new Category();
$category->name = $request->name;
$category->save();
}
}
and my servicerepositoryprovider
<?php
namespace App\Providers;
use App\Repository\CategoryRepository;
use App\Repository\CategoryRepositoryInterface;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->bind(CategoryRepositoryInterface::class, CategoryRepository::class);
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
You have typos in your RepositoryServiceProvier
use App\Repository\categoryRepository; //First alphabet should be lower case
use App\Repository\categoryRepositoryInterface; //First alphabet should be lower case
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
//First alphabet should be lower case as per your class and interface names
$this->app->bind(categoryRepositoryInterface::class, categoryRepository::class);
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
Would like to give you a piece of advice. When developing with Laravel it would be easier for you to follow Laravel's naming conventions - Laravel conventions overall. Following conventions will help you better understand examples on the internet as well.
Whatever you decide remember to follow them with consistency. In this particular case you are naming classes in camel case starting with small alphabet so when importing via use statements you need to stick to that.
I'm using Laravel 8 and I'm creating a custom Facade, but I cannot recall it with LogActivity::log($payload) but only with LogActivityFacade::log($payload).
Cannot see where is my fault...
app\Helpers\LogActivityFacade.php
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Facade;
class LogActivityFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'logactivity';
}
}
app\Helpers\LogActivityHelper.php
<?php
namespace App\Helpers;
use App\Repositories\LogActivityRepository;
class LogActivityHelper
{
public function log($payload)
{
$repository = new LogActivityRepository();
$repository->store($payload);
}
}
app\Providers\LogActivityServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\Facades\App;
use App\Helpers\LogActivityHelper;
use Illuminate\Support\ServiceProvider;
class LogActivityServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->bind('logactivity', function() {
return new LogActivityHelper();
});
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
config/app.php
In providers array
[...]
App\Providers\LogActivityServiceProvider::class,
In alias array
'LogActivity' => App\Helpers\LogActivityFacade::class,
I tried also composer dump-autoload and php artisan config:clear, but I can access to the Facade (and it works...) only with LogActivityFacade::log() instead of LogActivity.
This is the expected behavior. Laravel doesn't create new classes for you it just proxies methods from the service class in the facade using the __call magic method. If you take a peek at, for example, the Auth or Route facade in the vendor directory you will see that they are named Auth and Route respectively not AuthFacade and RouteFacade. So just name your facade LogActivity. If you need to differentiate it from the service class you can use namespacing or just postfix the service class name with something as you have already done.
You can do this for easy access to the facades
namespace App\Facade;
use Illuminate\Support\Facades\Facade;
abstract class BaseFacade extends Facade
{
/**
* #return string
*/
public static function getFacadeAccessor()
{
return static::class ;
}
/**
* #param $class
*/
static function shouldProxyTo($class)
{
app()->singleton(self::getFacadeAccessor(),$class);
}
}
extend other facades
namespace App\Facade\Plugins;
use App\Facade\BaseFacade;
/**
* #method static convertPersianNumberToEnglish($number)
* #method static bool checkDataIsTrue(array $results = [])
* #method static string|null removeFileTypeName(string $string = null)
*/
class GlobalPluginsFacade extends BaseFacade
{
}
register in services provider
public function boot()
{
// global facades
GlobalPluginsFacade::shouldProxyTo(GlobalPluginsRepo::class);
}
And easy to use.
GlobalPluginsFacade::getFunction();
In laravel 6 app I created facade app/Facades/MyFuncsClass.php :
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class MyFuncsClass extends Facade{
protected static function getFacadeAccessor() { return 'MyFuncsClass'; }
}
But I got error :
"message": "Non-static method App\\Helpers\\MyFuncsClass::debToFile() should not be called
statically",
calling it from boot method:
protected static function boot() {
parent::boot();
static::deleting(function($task) {
$hostel_image_image_path= Task::getTaskImagePath($task->id, $task->image);
MyFuncsClass::debToFile(print_r($hostel_image_image_path, true), '-9 $hostel_image_image_path::');
...
Is there is a way to escape this error and run MyFuncsClass::debToFile in boot method ?
MODIFIED :
Sure I registered my facade in config/app.php, 'providers' block :
...
App\Providers\MyFuncsClassProvider::class,
file app/Http/Helpers/MyFuncsClass.php has a lot of public methods, with heading:
<?php
namespace App\Helpers;
use Illuminate\Http\Request;
use Barryvdh\Debugbar\Facade as Debugbar;
use Carbon\Carbon;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
class MyFuncsClass {
public $concat_str_max_length = 50;
public $m_concat_str_add_chars = '...';
public function debToFile($contents, string $descr_text = '', bool $is_sql = false, string $file_name = '')
{
try {
...
and in app/Providers/MyFuncsClassProvider.php :
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\App;
class MyFuncsClassProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
App::bind('MyFuncsClass', function()
{
return new \App\Helpers\MyFuncsClass;
});
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
Actually I can call \MyFuncsClass::debToFile( ok from not static methods, like control actions, but I have the error
calling from static boot method...
MODIFIED # 2 :
With real time facades https://laravel.com/docs/5.7/facades#real-time-facades defintions
I tried
<?php
namespace App;
use DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use App\Facades\MyFuncsClass;
use App\Http\Helpers\MyFuncsClassContainer; // Alias for my helper class
use Illuminate\Database\Eloquent\Model;
use Barryvdh\Debugbar\Facade as Debugbar;
class Task extends Model
{
use Sluggable;
protected $table = 'tasks';
protected $primaryKey = 'id';
}
protected static function boot() {
parent::boot();
static::deleting(function($task) {
$hostel_image_image_path= Task::getTaskImagePath($task->id, $task->image);
\Log::info( '-9 $hostel_image_image_path::' );
\Log::info( print_r($hostel_image_image_path, true) );
$myFuncsClassCore = factory(MyFuncsClassContainer::class)->create();
$myFuncsClassCore->debToFile(' debToFile string REDSA');
Bur anywat I got error :
Cannot declare class App\Helpers\MyFuncsClassContainer, because the name is already in use {"userId":1,"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalErrorException
I tried a way to rename my helper class to “MyFuncsClassContainer”, supposing
having the same name for helper and facade could raise error, but failed
If there is a way to fix this error ?
Thanks!
I'm getting the following error:
FatalErrorException in EloquentVehicle.php line 30: Call to a member
function groupBy() on null
I have the following code:
<?php
namespace App\Project\Frontend\Repo\Vehicle;
use Illuminate\Database\Eloquent\Model;
class EloquentVehicle implements VehicleInterface
{
protected $vehicle;
/**
* EloquentVehicle constructor.
*
* #param Model $vehicle
*/
public function __construct(
Model $vehicle
)
{
$this->$vehicle = $vehicle;
}
/**
* Fetch unique makes
*
* #return mixed
*/
public function fetchMakes()
{
return $this->vehicle->groupBy(array('make'))
->orderBy('make', 'asc')
->get();
}
}
I've checked Illuminate\Database\Eloquent\Model for the method which is obviously not there, but I don't know what I should be adding to my class so that I can use the groupBy method. The laravel docs say the method exists.
UPDATE: Apparently I can't typehint an abstract class. I don't know how else I should be going about using Eloquent to retrieve records. If it helps, below is the code I have for registering the classes to the service container
<?php
namespace App\Providers;
use App\Vehicle;
use App\Project\Frontend\Repo\Vehicle\EloquentVehicle;
use Illuminate\Support\ServiceProvider;
class RepoServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('App\Project\Frontend\Repo\Vehicle\VehicleInterface', function($app)
{
return new EloquentVehicle(
new Vehicle
);
});
}
}
I just found my mistake and quite literally lay my face in my palms.
This
$this->$vehicle = $vehicle;
should be this
$this->vehicle = $vehicle;