how to getting the email verification link in Unit Test Laravel
Asked Answered
S

4

5

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.

  1. The first issue is that the email is not sent.
  2. 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?

Slacker answered 21/12, 2018 at 12:30 Comment(4)
Can you also include your register function?Loricate
Just built in laravel register functionSlacker
I have the same issue, the notification is not sent. I also had a problem where the user created by registerUser was not the one used in the test. I had to $user = User::orderBy('created_at', 'desc')->first(); in the test. It may be usefulWagner
At first be sure that your User model implements 'MustVerifyEmail' interface then check if your register function has Registered event called: event(new Registered($user));Inward
F
7

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);
    }
Flipflop answered 29/8, 2019 at 16:47 Comment(4)
Why do you consider it as "not a perfect solution"? I see no problem with this.Meyer
dunno. because of the extension that actually does nothing except that it gives me access to the underling method?Flipflop
Short and precise! thank you, here's a 50 rep bounty :)Quadruplet
arguably that laravel declares all functions as protected is inferior. I understand their thoughtWilkinson
G
5

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.

Gossamer answered 14/5, 2021 at 11:23 Comment(0)
I
4

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());
}
Immersionism answered 30/11, 2021 at 14:6 Comment(0)
T
0

The answer by @florent is great, however for Laravel 5.7.29 I find that the new user created using the factory automatically has its email_verified_at flag set, so the first test - that the user is NOT yet verified - fails.

With the addition of an array to set email_verified_at to null, correct operation is observed:

public function testVerifyEmailValidatesUser(): void
{
    // VerifyEmail extends Illuminate\Auth\Notifications\VerifyEmail in this example
    $notification = new VerifyEmail();
    $user = factory(User::class)->create([ 'email_verified_at' => null ]);

    // 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());
}
Timtima answered 8/11, 2023 at 23:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.