Laravel Mock should be called at least once but called 0 times - php

I have an artisan command that fires a job called PasswordResetJob which iterates as it calls a method forcePasswordReset in a repository class OrgRepository, the method updates a user's table. The whole process works fine.
Now I'm trying to write a Laravel test to mock the OrgRepository class and assert that the forcePasswordReset method is called at least once, which should be the case, based on the conditions I provided to the test. In the test, I call the artisan command to fire job; (I'm using sync queue for testing) this works fine as the job gets called and the user's table gets updated as I can view my database updates directly.
However, the test fails with the error: Mockery\Exception\InvalidCountException : Method forcePasswordReset() from Mockery_2_Repositories_OrgRepository should be called
at least 1 times but called 0 times.
The artisan call within the test is:
Artisan::call('shisiah:implement-org-password-reset');
I have tried to make the artisan call before, as well as after this mock initialization, but I still get the same errors. Here is the mock initialization within the test
$this->spy(OrgRepository::class, function ($mock) {
$mock->shouldHaveReceived('forcePasswordReset');
});
What am I missing? I have gone through the documentation and searched through Google for hours. Please let me know if you need any additional information to help. I'm using Laravel version 6.0
edit
I pass the OrgRepository class into the handle method of the job class, like this:
public function handle(OrgRepository $repository)
{
//get orgs
$orgs = Org::where('status', true)->get();
foreach ($orgs as $org){
$repository->forcePasswordReset($org);
}
}

The problem is that you are initializing your spy after your job has already run, which means during the job it will use the real class instead of the spy.
You have to do something like this in your test:
$spy = $this->spy(OrgRepository::class);
// run your job
$spy->shouldHaveReceived('forcePasswordReset');
We tell laravel to use the spy instead of the repository, run the job and then assert that the method was called.
Jeffrey Way explains it pretty well in this screencast.

Related

CakePHP 3 - executing multiple Commands from 1 Command and logging errors if they occur

I'm building an application in CakePHP 3.8 which uses Console Commands to execute several processes.
These processes are quite resource intensive so I've written them with Commands because they would easily time-out if executed in a browser.
There are 5 different scripts that do different tasks: src/Command/Stage1Command.php,
... src/Command/Stage5Command.php.
The scripts are being executed in order (Stage 1 ... Stage 5) manually, i.e. src/Command/Stage1Command.php is executed with:
$ php bin/cake.php stage1
All 5 commands accept one parameter - an ID - and then perform some work. This has been set up as follows (the code in buildOptionsParser() exists in each command):
class Stage1Command extends Command
{
protected function buildOptionParser(ConsoleOptionParser $parser)
{
$parser->addArgument('filter_id', [
'help' => 'Filter ID must be passed as an argument',
'required' => true
]);
return $parser;
}
}
So I can execute "Stage 1" as follows, assuming 428 is the ID I want to pass.
$ php bin/cake.php stage1 428
Instead of executing these manually, I want to achieve the following:
Create a new Command which loops through a set of Filter ID's and then calls each of the 5 commands, passing the ID.
Update a table to show the outcome (success, error) of each command.
For (1) I have created src/Command/RunAllCommand.php and then used a loop on my table of Filters to generate the IDs, and then execute the 5 commands, passing the ID. The script looks like this:
namespace App\Command;
use Cake\ORM\TableRegistry;
// ...
class RunAllCommand extends Command
{
public function execute(Arguments $args, ConsoleIo $io)
{
$FiltersTable = TableRegistry::getTableLocator()->get('Filters');
$all_filters = $FiltersTable->find()->toArray();
foreach ($all_filters as $k => $filter) {
$io->out($filter['id']);
// execute Stage1Command.php
$command = new Stage1Command(['filter_id' => $filter['id']]);
$this->executeCommand($command);
// ...
// execute Stage5Command.php
$command5 = new Stage5Command(['filter_id' => $filter['id']]);
$this->executeCommand($command5);
}
}
}
This doesn't work. It gives an error:
Filter ID must be passed as an argument
I can tell that the commands are being called because these are my own error messages from buildOptionsParser().
This makes no sense because the line $io->out($filter['id']) in RunAllCommand.php is showing that the filter IDs are being read from my database. How do you pass an argument in this way? I'm following the docs on Calling Other Commands (https://book.cakephp.org/3/en/console-and-shells/commands.html#calling-other-commands).
I don't understand how to achieve (2). In each of the Commands I've added code such as this when an error occurs which stops execution of the rest of that Command. For example if this gets executed in Stage1Command it should abort and move to Stage2Command:
// e.g. this code can be anywhere in execute() in any of the 5 commands where an error occurs.
$io->error('error message');
$this->abort();
If $this->abort() gets called anywhere I need to log this into another table in my database. Do I need to add code before $this->abort() to write this to a database, or is there some other way, e.g. try...catch in RunAllCommand?
Background information: The idea with this is that RunAllCommand.php would be executed via Cron. This means that the processes carried out by each Stage would occur at regular intervals without requiring manual execution of any of the scripts - or passing IDs manually as command parameters.
The arguments sent to the "main" command are not automatically being passed to the "sub" commands that you're invoking with executeCommand(), the reason for that being that they might very well be incompatible, the "main" command has no way of knowing which arguments should or shouldn't be passed. The last thing you want is a sub command do something that you haven't asked it to do just because of an argument that the main command makes use of.
So you need to pass the arguments that you want your sub commands to receive manually, that would be the second argument of \Cake\Console\BaseCommand::executeCommand(), not the command constructor, it doesn't take any arguments at all (unless you've overwritten the base constructor).
$this->executeCommand($stage1, [$filter['id']]);
Note that the arguments array is not associative, the values are passed as single value entries, just like PHP would receive them in the $argv variable, ie:
['positional argument value', '--named', 'named option value']
With regards to errors, executeCommand() returns the exit code of the command. Calling $this->abort() in your sub command will trigger an exception, which is being catched in executeCommand() and has its code returned just like the normal exit code from your sub command's execute() method.
So if you just need to log a failure, then you could simply evaluate the return code, like:
$result = $this->executeCommand($stage1, [$filter['id']]);
// assuming your sub commands do always return a code, and do not
// rely on `null` (ie no return value) being treated as success too
if ($result !== static::CODE_SUCCESS) {
$this->log('Stage 1 failed');
}
If you need additional information to be logged, then you could of course log inside of your sub commands where that information is available, or maybe store error info in the command and expose a method to read that info, or throw an exception with error details that your main command could catch and evaluate. However, throwing an exception would not be overly nice when running the commands standalone, so you'll have to figure what the best option is in your case.

Laravel Task schedule with cron column in database

I have a "reportSchedule" model which contains the report name and a cron_request column such as */15 * * * *.
I want to be able to adjust the cron within the database and affect the times which the report is requested. For example, the following is working from directly within the console/Kernel.php:
ReportSchedule::all()->each(function(ReportSchedule $reportSchedule) use($schedule){
if(isset($reportSchedule->cron_request)){
$schedule->call(function() use ($reportSchedule) {
ReportRequestNow::dispatch($reportSchedule);
})->cron($reportSchedule->cron_request);
}
});
However, having the model called from directly within the kernel causes other issues. For example database migrations now do not work and errors are thrown when caching the routes or running route:list. In general, it does not seem to like it!
So my idea was either create a seeder job or put this into its own schedule, however neither work.
// Doesnt work - the every minute schuedle is called but ReportRequestNow is never reached.
$schedule->call(function() use($schedule){
ReportSchedule::all()->each(function(ReportSchedule $reportSchedule) use($schedule){
if(isset($reportSchedule->cron_request)){
$schedule->call(function() use ($reportSchedule) {
ReportRequestNow::dispatch($reportSchedule);
})->cron($reportSchedule->cron_request);
}
});
})->everyMinute();
// Also does not work
$schedule->job(new ReportScheduleSeeder(), 'high')->everyMinute();
Can anyone suggest a why this does not work or how to get it working?
However, having the model called from directly within the kernel
causes other issues. For example database migrations now do not work
and errors are thrown when caching the routes or running route:list.
In general, it does not seem to like it!
Seems that there's some syntax errors (maybe some classes aren't listed in use?)
Have you checked laravel and PHP logs? Most likely there will be some explanations.

Laravel 5.6 - How to run job or event with parameters from command line?

I have an App\Jobs\BanUser job which accepts a parameter id to block a bad user by passing their id. That works perfectly if called from a controller like this:
dispatch(new BanUser($id));
But when I try to do that from the tinker command line with the full namespace like this:
dispatch(new App\Jobs\BanUser('1'));
I get this error:
PHP Fatal error: Class 'App/Jobs/BanUser' not found in Psy Shell code
on line 1
Any idea how to accomplish this job with the passed id parameter from the command line?
NOTE: If the solution requires starting up a queue from command line just for this job, maybe it's best to set it as an event? I don't want it to queue, just want to be able to run it in real time by passing parameter and executing the BanUser respective code from command line for either job or event.
Make sure you have correct namespace App\Jobs in your BanUser class.
Also check BanUser class file is placed in right directory.
try
app('Illuminate\Bus\Dispatcher')->dispatch(new App\Jobs\BanUser('1'));
I am not sure if it will work but, you can give it a try.

How to get currently used Artisan console command name in Laravel 5?

Problem / What I've tried:
Getting the currently used controller and action in Laravel 5 is easy (but not as easy as it should be), however I'm stuck with getting the currently used artisan console command.
To fetch the controller name I do this:
$route = Route::getRoutes()->match(Request::capture());
$listAction = explode('\\', $route->getActionName());
$rawAction = end($listAction);
// controller name and action in a simple array
$controllerAndAction = explode('#', $rawAction);
But when calling from a console action, it always returns the default index controller's name ("IndexController" or so in Laravel). Does anybody know how to make this ?
By the way I've also worked throught Request::capture() but this still gives no info about the command.
The simplest way is to just to look at the arguments specified on the command line:
if (array_get(request()->server(), 'argv.1') === 'cache:clear') {
// do things
}
Yes, you can use $_SERVER directly, but I like to use the helper functions or the Facades, as those will give you the current data.
I go from the assumption that - during unit tests - the superglobals might not always reflect the currently tested request.
By the way: Obviously can also do array_get(request()->server('argv'), '1') or something alike. (request()->server('argv.1') doesnt work at this point). Or use \Request::server(). Depends on what you like most.
As per the Symfony\Component\Console\Command\Command class, the method to return the name of the command (eg. my:command) is:
$this->getName();
You should use it from within an Artisan command extending Illuminate\Console\Command (default on Artisan commands).
Remember that it will return only the command name and not the available parameters (eg. for the command signature my:command {--with-params=} it will only return my:command).
Reflection might be of help? Try this:
$var = new \ReflectionClass($this);
dd($var);

phpunit check logs file

I have several integration tests with phpunit,
and in the proccess of the tests there are some logs written to files in the system.
I would like to check if a line was written during a test, is that possible?
example:
/** #test */
function action_that_writes_to_log() {
$this->call('GET', 'path/to/action', [], [], $requestXml);
//I want this:
$this->assertFileHas('the log line written', '/log/file/path.log');
}
The obvious way:
Implementing a custom assertion method, like the one you propose: assertFileHas. It's quite easy, just check if the string appears in the file. The problem you can get is that the line can already exist from another test or the same test already run. A possible solution for this is deleting the logs content before each test or test class, depending on your needs. You would need a method that deletes the logs and call it from setUp or setUpBeforeClass.
I would go with another approach: mocking the logging component, and checking that the right call is being done:
$logger_mock->expects($this->once())
->method('log')
->with($this->equalTo('the log line written'));
This makes easy to test that the components are logging the right messages, but you also need to implement a test that verifies that the logger is capable of actually writting to the file. But it's easier to implement that test once, and then just check that each component calls the logging method.

Categories