I have a unit test with the following:
use \Illuminate\Notifications\DatabaseNotification;
public function testMailSentAndLogged()
{
Notification::fake();
$user = factory(User::class)->create();
$emailAddress = $user->emailAddress;
$emailAddress->notify(new UserCreated);
Notification::assertSentTo(
$emailAddress,
UserCreated::class
);
error_log('DatabaseNotification '.print_r(DatabaseNotification::get()->toArray(), 1));
$this->assertEquals(1, $emailAddress->notifications->count());
}
My Notification has this for the via():
public final function via($notifiable)
{
// complex logic...
error_log('mail, database');
return ['mail', 'database'];
}
The code fails on the $this->assertEquals code. the error_log produces the following:
[03-Jan-2018 01:23:01 UTC] mail, database
[03-Jan-2018 01:23:01 UTC] DatabaseNotification Array
(
)
WHY don't the $emailAddress->notifications pull up anything? Why doesn't DatabaseNotification::get() pull anything?;
In your test, you are calling the method
Notification::fake();
As stated in Laravel's documentation on Mocking,
You may use the Notification facade's fake method to prevent
notifications from being sent.
Actually, this bit of code is the assertion that the Notification would have been sent, under normal circumstances (ie in prod) :
Notification::assertSentTo();
If you remove the call to Notification::fake(), your notification should appear in your testing database.
So you kinda have two solutions. The first one is to remove the call to fake(), thus really sending the notification, which will appear in the database. the second is not to test if the notification was written successfully in the database : it's Laravel's responsibility, not your application's. I recommand the second solution :)
Related
I went through notification testing and have to check the notification routing but I have problem when it comes to group assertions. For a single receiver, all tests passes with the default Notification::assertSentTo method. But things were different when it goes to group notification. I have tried to swap between 3 approaches;
This approach gave a warning stating that This test did not perform any assertions
$author->company->employees()->role('hrd')->each(function ($hrd) {
Notification::assertSentTo($hrd, LeaveRequestNotification::class);
});
Same warning, but with foreach
foreach($author->company->employees->role('hrd') as $hrd) {
Notification::assertSentTo($hrd, LeaveRequestNotification::class);
}
BadMethodCallException with test going red.
Notification::assertSentTo(
$author->company->employees()->role('hrd'),
LeaveRequestNotification::class,
);
Here is the complete error message occured by the 3rd approach
$ test --testsuite=Unit --filter=LeaveTest::testShouldSendRequestNotificationToHrdsWhenAuthorIsRegularEmployee --stop-on-failure
FAIL Tests\Unit\LeaveTest
⨯ should send request notification to hrds when author is regular employee
---
• Tests\Unit\LeaveTest > should send request notification to hrds when author is regular employee
BadMethodCallException
Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::getKey()
So, what is the proper way for doing this?
Just found the problem, it was lying here on the assignRole loop, I used the wrong subject
$leave->company->employees
instead of
$author->company->employees
The loop:
foreach ($leave->company->employees as $employee) {
if (!$employee->hasRole('employee')) {
$employee->assignRole('hrd');
}
}
dd($author->company->employees()->role('hrd')->count()); // prints 0
I changed the subject and use the 3rd approach, only this time, I add get() after the role definition. Works fine with no error. Thanks for #mrhn for reminding me about the existence of the loop subject.
foreach ($author->company->employees as $employee) {
if (!$employee->hasRole('employee')) {
$employee->assignRole('hrd');
}
}
LeaveRequest::dispatch($leave);
Notification::assertSentTo(
$author->company->employees()->role('hrd')->get(),
LeaveRequestNotification::class,
);
When a job fails my notification doesn't arrive, and I'm not sure why. In my Job I have:
use Notification;
use App\Notifications\MonitoringMessage;
public function handle()
{
asdf;
}
public function failed(Exception $exception)
{
$message = ':warning: A job failed.';
Notification::route('slack', config('services.slack.webhook'))->notify(new MonitoringMessage($message));
}
The notification is not queued using use Queueable; etc because I've read that that might cause the issue because the job itself is also queued.
The code above will cause the job to fail of course, and I can see it in the failed_jobs table, but the notification is not send. If I put the notification code somewhere else (eg in a controller) and execute it the notification is sent so that code is correct.
Any job is handled through the handle() method. Make sure this method doesn't fail because of a syntax mistake. If your code fails on a syntax error, the rest of you code can't possibly be executed as it will never be processed. Force a conceptual mistake, for example a division by zero forced error:
use Notification;
use App\Notifications\MonitoringMessage;
public function handle()
{
$result = 1/0;
}
public function failed()
{
$message = ':warning: A job failed.';
Notification::route('slack', config('services.slack.webhook'))->notify(new MonitoringMessage($message));
}
As you have stated yourself, the Exception class implementation is not needed because it's not used.
Well basically what I need is to delete some files from the storage folder, but I want that to happen every time a request is finished.
I have already created an event and a listener that is responsible for doing that, which is called before doing the return in the controller. But since some exception may happen, the event is obviously not going to be launched.
I need that to always run regardless of whether an exception occurs or not.
The laravel documentation https://laravel.com/docs/4.2/lifecycle#application-events talks about it but it is in version 4.2 and that does not have the current version
Use the AfterMiddleware:
This middleware would perform its task after the request is handled by the application:
class AfterMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
// Perform action
return $response;
}
}
https://laravel.com/docs/5.5/middleware#defining-middleware
I've created a mail notification that works successfully, but when trying to queue it, I get the following error:
Uncaught Exception: Serialization of 'Closure' is not allowed in /vendor/laravel/framework/src/Illuminate/Queue/Queue.php:125
Below is my code that I believe is causing the error:
public function toMail($notifiable)
{
$view_file = 'emails.verifyEmail';
$view = View::make($view_file, ['invitationToken' => $this->invitationToken, 'team_name' => $this->team->name, 'team_domain' => $this->team->domain ]);
$view = new HtmlString(with(new CssToInlineStyles)->convert($view));
return (new MailMessage)
->subject('Email Verification')
->view('emails.htmlBlank', ['bodyContent' => $view]);
}
I am not exactly sure where the 'Closure' it's trying to serialize is coming from. I tried tacking on ->render() to the end of View::make but that did not seem to make a difference. I believe it may have something to do with the view function of the MailMessage but I'm not really sure.
Once again, this notification works perfectly when it is not being Queued.
Any help would be appreciated.
Even if the question is pretty old, i'm posting this for future reference.
The problem occurs when the Queue tries to serialize the notification instance. This is done by serializing every property of the notification object. I had the same problem because i was doing something like
public function __construct(\Exception $ex){
$this->exception = $exception;
}
in my notification class.
Once the notification is wrapped in SendQueuedNotification it will be serialized by the Queue handler. During this process every property of SendQueuedNotification will be serialized, including our custom notification instance and its properties. Everything will fail when the serializer will try to serialize $exception instance; for some reason the exception class is unserializable because it probably contains a closure within its properties. So what worked for me was changing the constructor as follows
public function __construct(\Exception $ex)
{
$this->exceptionClass = get_class($ex);
$this->exceptionMessage = $ex->getMessage();
$this->exceptionLine = $ex->getFile() . '#' . $ex->getLine();
$this->exceptionCode = $ex->getCode();
}
Now all of the notification properties are fully serializable and everything works as expected.
Another solution is to use __wakeup() and __sleep() methods to customize the serialization and deserialization of your notification instance.
Hope it helps to understand your issue.
I've got a Controller.php whose show($id) method is hit by a route.
public function show($id)
{
// fetch a couple attributes from the request ...
$this->checkEverythingIsOk($attributes);
// ... return the requested resource.
return $response;
}
Now, in checkEverythingIsOk(), I perform some validation and authorization stuff. These checks are common to several routes within the same controller, so I'd like to extract these checks and call the method everytime I need to perform the same operations.
The problem is, I'm unable to send some responses from this method:
private function checkEverythingIsOk($attributes)
{
if (checkSomething()) {
return response()->json('Something went wrong'); // this does not work - it will return, but the response won't be sent.
}
// more checks...
return response()->callAResponseMacro('Something else went wrong'); // does not work either.
dd($attributes); // this works.
abort(422); // this works too.
}
Note: Yes, I know in general one can use middleware or validation services to perform the checks before the request hits the controller, but I don't want to. I need to do it this way.
As of Laravel 5.6 you can now use for example response()->json([1])->send();.
There is no need for it to be the return value of a controller method.
Note that calling send() will not terminate the output. You may want to call exit; manually after send().
You are probably looking for this:
function checkEverythingIsOk() {
if (checkSomething()) {
return Response::json('Something went wrong');
}
if(checkSomethingElse()) {
return Response::someMacro('Something else is wrong')
}
return null; // all is fine
}
And in the controller method:
$response = $this->checkEverythingIsOk();
if($response !== null) { // $response instanceof Response
return $response;
}
It's probably overkill, but I will throw it in anyway. You might want to look into internal requests. Also this is just pseudoish code, I have not actually done this, so take this bit of information with caution.
// build a new request
$returnEarly = Request::create('/returnearly');
// dispatch the new request
app()->handle($newRequest);
// have a route set up to catch those
Route::get('/returnearly', ...);
Now you can have a Controller sitting at the end of that route and interpret the parameters, or you use multiple routes answered by multiple Controllers/Methods ... up to you, but the approach stays the same.
UPDATE
Ok I just tried that myself, creating a new request and dispatching that, it works this way. Problem is, the execution does not stop after the child-request has exited. It goes on in the parent request. Which makes this whole approach kind of useless.
But I was thinking about another way, why not throw an Exception and catch it in an appropriate place to return a specified response?
Turns out, thats already built into Laravel:
// create intended Response
$response = Response::create(''); // or use the response() helper
// throw it, it is a Illuminate\Http\Exception\HttpResponseException
$response->throwResponse();
Now usually an Exception would be logged and you if you are in Debug mode, you would see it on screen etc. etc. But if you take a look into \Illuminate\Foundation\Exceptions\Handler within the render method you can see that it inspects the thrown Exception if it is an instance of HttpResponseException. If it is then the Response will be returned immediately.
To me the most simple and elegant way is:
response()->json($messages_array, $status_code)->throwResponse();
(you don`t need return)
It can be called from a private function or another class...
I use this in a helper class to check for permissions, and if the user doesn`t have it I throw with the above code.