I am testing notifications in my application and here is my code to test that Admin receives a notification:
public function testAdminReceivesNotifications()
{
Notification::fake();
$admin = $this->preload();
$taskTitle = str_random();
$project = Project::first();
$projectMember = factory(\App\Models\User::class)->create([
'type' => 'INTERNAL'
]);
$project->users()->save($projectMember, [
'project_permissions' => [1,2,3,4,5,6,7,8,10]
]);
$this->browse(function (Browser $browser, Browser $secondBrowser) use($admin, $taskTitle, $project, $projectMember) {
$this->login($browser, $admin)
->visit("p/{$project->slug}")
->waitForText('Add Task')
->click("#add-task")
->waitForText('Create Task')
->type('#task-title', $taskTitle)
->keys('[trix-id="1"]', str_random())
->press('SAVE TASK')
->waitForText('Post anyway')
->press('Post anyway')
->waitForText('Unscheduled')
->pause(1000)
->assertSee('Unscheduled')
->assertSee($taskTitle);
$task = Project\Todo::where('title', $taskTitle)->first();
$this->login($secondBrowser, $projectMember)
->visit("p/{$project->slug}/tasks/{$task->id}")
->waitForText($task->title)
->keys('trix-editor', str_random() . "message by another member")
->press('Reply')
->pause(2000);
});
Notification::assertSentTo($admin,TodoMessageCreated::class);
}
The problem I am getting - is even though I do set Notification fake - the email is being sent (I use log as Mail driver in my tests and I see the email in the logs. I have checked - an I can assure that TodoMessageCreated was triggered.
Related
In laravel 9, breeze 1.11 app I want to make feature test for forgot-password functionality and in routes I found :
GET|HEAD
In laravel 9, breeze 1.11 app I want to make feature test for forgot-password functionality and in routes I found :
GET|HEAD forgot-password ... password.request › Auth\PasswordResetLinkController#create
POST forgot-password ........ password.email › Auth\PasswordResetLinkController#store
So I make :
test to check opened form :
public function testAdminForgetPasswordFormOpened()
{
$response = $this->get(route('password.request'));
$response->assertStatus(200);
$response->assertViewIs('auth.forgot-password');
$response->assertSessionHasNoErrors();
}
and it works ok. But I failed to check how token is sent when user submit form with email entered. I do :
public function testAdminGotPasswordResetLinkEmail()
{
Notification::fake();
$loggedAdmin = User::factory()->make();
$response = $this->post(route('password.email'), [
'email' => $loggedAdmin->email,
]);
$token = DB::table('password_resets')->first();
Notification::assertSentTo(
$loggedAdmin,
SubscriptionEmailingNotification::class,// that is my my Notification class
function ($notification) use ($token) { // https://laravel.com/docs/9.x/mocking#notification-fake
\Log::info(varDump($notification, ' -1 $notification::')); / I DO NOT SEE THESE LOG MESSAGES
\Log::info(varDump($token, ' -12 $token::'));
return Hash::check($notification->token, $token->token) === true;
}
);
}
But I got error :
1) Tests\Feature\AuthTest::testAdminGotPasswordResetLinkEmail
The expected [App\Notifications\SubscriptionEmailingNotification] notification was not sent.
Failed asserting that false is true.
/mnt/_work_sdb8/wwwroot/lar/MngProducts/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:83
/mnt/_work_sdb8/wwwroot/lar/MngProducts/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:338
/mnt/_work_sdb8/wwwroot/lar/MngProducts/tests/Feature/AuthTest.php:226
Looking how it works in breeze I see method :
$status = Password::sendResetLink(
$request->only('email')
);
I did not find how method above is implemented and which notification it uses ?
I suppose that some notification is used here, but not sure...
I found declaration of assertSentTo method as :
public static function assertSentTo($notifiable, $notification, $callback = null)
{
How that tests must be done ?
Thanks!
You shouldn't check the token in the email if you continue to use the Password::sendResetLink(...) method. Breeze already has its own tests for this. Proving the Password::sendResetLink(...) method is successfully called will be enough to confirm faultless integration. You can verify it by checking the "ResetPassword" notification:
Notification::assertSentTo($user, ResetPassword::class);
However, if you still want to check the token, you can use the sample code below, which took from Breeze's tests
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Support\Facades\Notification;
// ...
public function test_password_can_be_reset_with_valid_token()
{
Notification::fake();
$user = User::factory()->create();
$this->post('/forgot-password', ['email' => $user->email]);
Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
$response = $this->post('/reset-password', [
'token' => $notification->token,
'email' => $user->email,
'password' => 'password',
'password_confirmation' => 'password',
]);
$response->assertSessionHasNoErrors();
return true;
});
}
Source: https://github.com/laravel/breeze/blob/v1.11.0/stubs/default/tests/Feature/PasswordResetTest.php#L50
I have a working code where when a Customer record is created, an event will dispatch and a listener will then send a notification to the Customer's agent.
EventServiceProvider.php
protected $listen = [
'App\Events\CustomerCreated' => [
'App\Listeners\TriggerExternalCustomerCreation',
'App\Listeners\SendNotificationCustomerCreated',
],
]
SendNotificationCustomerCreated.php
public function handle(CustomerCreated $event)
{
$when = now()->addMinutes(0);
$event->customer->manager->notify((new CustomerCreatedNotification($event->customer))->delay($when));
}
Here's my test case:-
public function customer_created_event_dispatch()
{
// $this->markTestIncomplete('This test has not been implemented yet.');
$this->withoutExceptionHandling();
Event::fake();
Notification::fake();
$user = factory(User::class)->states('order management')->create();
$data = $this->data(['address' => true, 'contact' => true]);
$response = $this->actingAs($user)->post(route('customers.store'), $data);
$response->assertSessionHasNoErrors();
$customers = Customer::all();
$customer = $customers->first();
$manager = $customer->manager;
$this->assertCount(1, $customers);
Event::assertDispatched(CustomerCreated::class, function ($event) use ($customers) {
return $event->customer->id === $customers->first()->id;
});
Notification::assertSentTo(
$manager,
CustomerCreatedNotification::class,
function ($notification, $channels) use ($customer) {
return $notification->customer->id === $customer->id;
}
);
}
Initially the listener is queued, but i try removed from queue but error remains.
I can confirmed that the event did dispatch since it passed the Event::assertDispatched. But failed at the last assertion with below errors:-
The expected [App\Notifications\CustomerCreatedNotification] notification was not sent
Faking event overwrites ordinary event logic and therefor will not trigger the listeners. This is useful as you sometimes need to block event chains from firing. Faking is also about not caring about side effects, because they are often really hard to test.
Then how do you test that your code work, i prefer to separate Event side effect testing into unit testing. Same approach if i where to test jobs, as the side effects are harder to assert and tests get quite huge if you need to test an endpoint plus a job chain.
Remove the assert notification from your original test. Create an new one in tests/Unit/TestCustomerCreatedEvent or whatever path makes sense.
public function customer_event_listener_tests() {
// create your data
$customers = Customer::all();
$customer = $customers->first();
$manager = $customer->manager;
// fake notification only
Notification::fake();
$event = new CustomerCreated($customer);
event($event);
Notification::assertSentTo(
$manager,
CustomerCreatedNotification::class,
function ($notification, $channels) use ($customer) {
return $notification->customer->id === $customer->id;
}
);
}
I am developing a Laravel application. I am doing unit testing to my application. Now I am having a trouble with testing the verification process.
What I am trying to do now is that I am registering a user, then test if the verification email is sent, then I will get the verification link for that registration, then I will do something with that link.
The first issue is that the email is not sent.
The second issue is that I do not know how to retrieve the
verification email link?
This is my test
public function test_user_can_be_verified_and_redirected_based_on_role()
{
Notification::fake();
$user = $this->registerUser();
Notification::assertSentTo($user, SendEmailVerificationNotification::class);
}
protected function registerUser()
{
$user = factory(User::class)->make();
$this->post(route('register'), [
'name' => $user->name,
'email' => $user->email,
'password' => 'testing',
'password_confirmation' => 'testing',
])->assertRedirect();
return User::whereEmail($user->email)->first();
}
But the issue is that the notification is not sent even if it is sent when I register from the browser. I also like to retrieve the verification link and do something. How can I do that?
Not a perfect solution but it does the trick, if somebody breaks this functionality somehow I'll know about it. :)
First overwrite the protected method verificationUri of the VerifyEmail notification and make it public
class EmailVerificationNotification extends VerifyEmail
{
public function verificationUrl($notifiable) {
return parent::verificationUrl($notifiable);
}
}
Then use it to generate a link and assert against it..
/** #test */
public function an_user_can_verify_his_email_address()
{
$notification = new EmailVerificationNotification();
$user = factory(User::class)->create(['email_verified_at' => null]);
$uri = $notification->verificationUrl($user);
$this->assertSame(null, $user->email_verified_at);
$this->actingAs($user)->get($uri);
$this->assertNotNull($user->email_verified_at);
}
A verification URL can be generated outside of the VerifyEmail notification easily by simply running:
$verificationUrl = URL::temporarySignedRoute(
'verification.verify',
now()->addMinutes(60),
['id' => $user->id, 'hash' => sha1($user->email)]
);
No need to make the verificationUrl() method public.
Here is my solution:
public function testVerifyEmailValidatesUser(): void
{
// VerifyEmail extends Illuminate\Auth\Notifications\VerifyEmail in this example
$notification = new VerifyEmail();
$user = factory(User::class)->create();
// New user should not has verified their email yet
$this->assertFalse($user->hasVerifiedEmail());
$mail = $notification->toMail($user);
$uri = $mail->actionUrl;
// Simulate clicking on the validation link
$this->actingAs($user)
->get($uri);
// User should have verified their email
$this->assertTrue(User::find($user->id)->hasVerifiedEmail());
}
I am using laravel 5.2 where I need to sent an OTP code to reset password, though email is being sent with built in subject and limited message done by make:auth command but how to customize? I have tried to follow the link unfortunately I am unable to understand how i can use this to solve.
I customized the api like this
public function sendResetLinkEmail(Request $request)
{
$this->validateSendResetLinkEmail($request);
$broker = $this->getBroker();
$email = $request->input('email');
$userid = DB::table('users')->where('email','=',$email)->value('id');
$uniqueotp = "DIYA".uniqid();
$curr_timestamp = strtotime(date("Y-m-d H:i:s"));
$date = strtotime("+7 day", $curr_timestamp);
$expiry_otp = date('Y-m-d H:i:s',$date);
$ip_address = $request->ip();
DB::table('otp_users')->insert([
'user_id' => $userid,
'status' => 0,
'otp_code' => $uniqueotp,
'ipaddress'=>$ip_address,
'expires_at'=>$expiry_otp
]);
$response = Password::broker($broker)->sendResetLink(
$this->getSendResetLinkEmailCredentials($request),
$this->resetEmailBuilder()
);
switch ($response) {
case Password::RESET_LINK_SENT:
return $this->getSendResetLinkEmailSuccessResponse($request,$response);
case Password::INVALID_USER:
default:
return $this->getSendResetLinkEmailFailureResponse($response);
}
}
Any idea how I can achieve?
My required email message like this:
Hello, Tamaghna Banerjee Click here to reset your password:
Your OTP is: B16445512121
Reset Your Password through http://localhost/diya/public/password/reset/83baba9f61fc851b9d80b515415ec86c43b03b56b068e1888256db7a7831ba83?email=tamaghnabanerjee%40live.com
I have a laravel 5 app and need to send a reset password link through a service sendinblue. How can I change the core functionality to use sendinblue in PasswordBroker.php?
public function emailResetLink(
CanResetPasswordContract $user,
$token,
Closure $callback = null
) {
$mailin = new Mailin(
'https://api.sendinblue.com/v2.0',
'0TYSSJBSKERNDKW'
);
$view = $this->emailView;
return $this->mailer->send(
$view,
compact('token', 'user'),
function($m) use ($user, $token, $callback)
{
$m->to($user->getEmailForPasswordReset());
if ( ! is_null($callback))
{
call_user_func($callback, $m, $user, $token);
}
});
}
Did you try to add Sendinblue as a mail driver? This github repo can help (https://github.com/agence-webup/laravel-sendinblue)
Here all your emails will be sent by Sendinblue and you will send as a regular mail in Laravel (https://laravel.com/docs/5.1/mail)
If is just for this, you can change the driver just for this kind of mail, i think that you can change the driver on runtime like this
Config::set('mail.driver', 'driver_name');
(new Illuminate\Mail\MailServiceProvider(app()))->register();
Also, you can try to listen to 'mailer.sending' event that is fired just before sending mail messages, but this is not a good approach.