Testing Laravel Password Resets

Edit the PasswordResetTest class by adding the method below.

By convention each test case method starts with test which is then recognized by PHPUnit.

/** * Testing showing the password reset request page.

*/public function testShowPasswordResetRequestPage(){ $this ->get(route(self::ROUTE_PASSWORD_REQUEST)) ->assertSuccessful() ->assertSee('Reset Password') ->assertSee('E-Mail Address') ->assertSee('Send Password Reset Link');}In this test case we use the method get to make a GET request to the specified URI.

We generate the URI using the route helper method and the name of our route, which is stored in a constant.

The assertSuccessful method asserts the response has a 200 level status code.

Next we use the assertSee method to check for the presence of the text Reset Password, E-Mail Address, and Send Password Reset Link.

Testing Submitting the Password Reset Request PageOur next few tests will be testing submitting the password reset request page with various inputs.

Add the next test shown below which tests submitting a password reset request with an invalid email address.

/** * Testing submitting the password reset request with an invalid * email address.

*/public function testSubmitPasswordResetRequestInvalidEmail(){ $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_REQUEST)) ->post(route(self::ROUTE_PASSWORD_EMAIL), [ 'email' => str_random(), ]) ->assertSuccessful() ->assertSee(__('validation.

email', [ 'attribute' => 'email', ]));}When a request fails validation, Laravel will return a redirect to the location the request came from with validation error messages flashed to the session.

To make assertions on the response the user will see, therefore, we need to follow redirects with the followingRedirects method.

We also specify a location we’re making the request from using the from method.

Next we use the post method to issue a POST request to the password.

email route (again using the route helper and a previously defined constant) with data specifying the email key as a random string (using the str_random helper method).

We assert the response is successful and check for the presence of a validation message.

The __ helper method is used to format the validation message using localization files.

validation.

email specifies the file resources/lang/{locale}/validation.

php and the email array key, where {locale} is the application’s configured locale.

The :attribute parameter in the string The :attribute must be a valid email address.

will be replaced by the string email as specified by the associative array passed as the second argument to the __ method.

Next we’ll be testing submitting the password reset request page with a valid email address that is not in use by any user of the application.

Add the test shown below.

/** * Testing submitting the password reset request with an email * address not in the database.

*/public function testSubmitPasswordResetRequestEmailNotFound(){ $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_REQUEST)) ->post(route(self::ROUTE_PASSWORD_EMAIL), [ 'email' => $this->faker->unique()->safeEmail, ]) ->assertSuccessful() ->assertSee(e(__('passwords.

user')));}Again we follow redirects and set the location where our request should originate from, but this time we use Faker to generate an email address that is not in use by anyone in the world (as the domains are example.

com, example.

net, and example.

org).

We use the unique method to ensure the email address returned has not been previously returned by Faker.

We assert the response is successful and check for the presence of the validation error message specified by the user key in the associative array in the file resources/lang/{locale}/passwords.

php.

This time the validation message contains a reserved HTML character, ', so we must use the e helper method to replace the character with it’s corresponding character entity.

Finally it’s time to test successfully submitting the password reset request page with a valid email address present in our application’s database.

Add the test shown below.

/** * Testing submitting a password reset request.

*/public function testSubmitPasswordResetRequest(){ $user = factory(User::class)->create(); $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_REQUEST)) ->post(route(self::ROUTE_PASSWORD_EMAIL), [ 'email' => $user->email, ]) ->assertSuccessful() ->assertSee(__('passwords.

sent')); Notification::assertSentTo($user, ResetPassword::class);}In this test we use the factory helper method to create a new user in our database.

Then we follow redirects for the response to our POST request to the password.

email route.

Our request specifies the created user’s email address in the email key of the payload.

We assert the response is successful and check for the presence of the string We have e-mailed your password reset link!, specified with the argument passwords.

sent passed to the __ helper method.

Using the Notification facade’s method assertSentTo we assert the ResetPassword notification was sent to the $user.

We can pass the model stored in the variable $user directly into the assertSentTo method because our User model, by default, uses the IlluminateNotificationsNotifiable trait.

When routing emails for any model using the Notifiable trait, the email property on the model will be used by default.

Testing Showing the Password Reset PageNext, to test showing the password reset page, add the test shown below.

/** * Testing showing the reset password page.

*/public function testShowPasswordResetPage(){ $user = factory(User::class)->create(); $token = Password::broker()->createToken($user); $this ->get(route(self::ROUTE_PASSWORD_RESET, [ 'token' => $token, ])) ->assertSuccessful() ->assertSee('Reset Password') ->assertSee('E-Mail Address') ->assertSee('Password') ->assertSee('Confirm Password');}We again create a user using the factory helper method.

Next we create a valid password reset token using the Password facade.

The value of $token is used to replace the token parameter in the password.

reset route.

We send a GET request to this route, assert the response is successful, and check for the presence of the text for page elements.

Testing Submitting the Password Rest PageNext we’ll test submitting the password reset page, starting with using an invalid email address.

Continue our testing by adding the test shown below.

/** * Testing submitting the password reset page with an invalid * email address.

*/public function testSubmitPasswordResetInvalidEmail(){ $user = factory(User::class)->create([ 'password' => bcrypt(self::USER_ORIGINAL_PASSWORD), ]); $token = Password::broker()->createToken($user); $password = str_random(); $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_RESET, [ 'token' => $token, ])) ->post(route(self::ROUTE_PASSWORD_RESET_SUBMIT), [ 'token' => $token, 'email' => str_random(), 'password' => $password, 'password_confirmation' => $password, ]) ->assertSuccessful() ->assertSee(__('validation.

email', [ 'attribute' => 'email', ])); $user->refresh(); $this->assertFalse(Hash::check($password, $user->password)); $this->assertTrue(Hash::check(self::USER_ORIGINAL_PASSWORD, $user->password));}In this test we’re again using the factory helper method to create at test user but this time we explicitly set the user’s password.

To do this we use the bcrypt helper method to hash the value of our constant.

We create another password reset token and generate a random string to use as the new password for our request’s payload.

Again following redirects we POST to the password.

reset.

submit route with a request originating from the password.

reset route.

A random string is used for the email address in the request payload.

After asserting the response was successful and checking for the validation.

email validation message we refresh the user model and use the check method on the Hash facade to assert the user’s password has not changed.

Next we’ll test submitting the password reset page with an email address not in use by our application’s database.

Add the test shown below.

/** * Testing submitting the password reset page with an email * address not in the database.

*/public function testSubmitPasswordResetEmailNotFound(){ $user = factory(User::class)->create([ 'password' => bcrypt(self::USER_ORIGINAL_PASSWORD), ]); $token = Password::broker()->createToken($user); $password = str_random(); $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_RESET, [ 'token' => $token, ])) ->post(route(self::ROUTE_PASSWORD_RESET_SUBMIT), [ 'token' => $token, 'email' => $this->faker->unique()->safeEmail, 'password' => $password, 'password_confirmation' => $password, ]) ->assertSuccessful() ->assertSee(e(__('passwords.

user'))); $user->refresh(); $this->assertFalse(Hash::check($password, $user->password)); $this->assertTrue(Hash::check(self::USER_ORIGINAL_PASSWORD, $user->password));}Nothing new on this test.

We create a user, password reset token, and new password.

Then the follow redirects, POST to the password.

reset.

submit route from the password.

reset route using the token, a random and unique safe email, and the new password.

We assert the response is successful, check for the presence of the passwords.

user translated string (after swapping any html character entities in the string), refresh the user, and assert the user’s password hasn’t changed.

The next test will be testing submitting the password reset page with a password that doesn’t match the password confirmation.

Add the test shown below.

/** * Testing submitting the password reset page with a password * that doesn't match the password confirmation.

*/public function testSubmitPasswordResetPasswordMismatch(){ $user = factory(User::class)->create([ 'password' => bcrypt(self::USER_ORIGINAL_PASSWORD), ]); $token = Password::broker()->createToken($user); $password = str_random(); $password_confirmation = str_random(); $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_RESET, [ 'token' => $token, ])) ->post(route(self::ROUTE_PASSWORD_RESET_SUBMIT), [ 'token' => $token, 'email' => $user->email, 'password' => $password, 'password_confirmation' => $password_confirmation, ]) ->assertSuccessful() ->assertSee(__('validation.

confirmed', [ 'attribute' => 'password', ])); $user->refresh(); $this->assertFalse(Hash::check($password, $user->password)); $this->assertTrue(Hash::check(self::USER_ORIGINAL_PASSWORD, $user->password));}Again nothing new on this test except we’re checking for a different validation message.

Our last invalid submission case to test for submitting the password reset page is using a new password that’s too short.

Add the test shown below.

/** * Testing submitting the password reset page with a password * that is not long enough.

*/public function testSubmitPasswordResetPasswordTooShort(){ $user = factory(User::class)->create([ 'password' => bcrypt(self::USER_ORIGINAL_PASSWORD), ]); $token = Password::broker()->createToken($user); $password = str_random(5); $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_RESET, [ 'token' => $token, ])) ->post(route(self::ROUTE_PASSWORD_RESET_SUBMIT), [ 'token' => $token, 'email' => $user->email, 'password' => $password, 'password_confirmation' => $password, ]) ->assertSuccessful() ->assertSee(__('validation.

min.

string', [ 'attribute' => 'password', 'min' => 6, ])); $user->refresh(); $this->assertFalse(Hash::check($password, $user->password)); $this->assertTrue(Hash::check(self::USER_ORIGINAL_PASSWORD, $user->password));}This time we pass an argument 5 to the str_random helper function to specify the length of the random returned string (as opposed to the default length of 16).

Another difference in this test is we’re checking for the presence of a validation message, validation.

min.

string, with two parameters, attribute and min.

Notice how we can use dot notation to specify a translation string in a nested array.

To learn more about these validation messages and translation strings, check out the file at resources/lang/{locale}/validation.

php.

Finally, it’s time to test the happy path: submitting the password reset page with a valid email address belonging to a user with a valid password reset token and a password matching the confirmation password (that isn’t too short).

Add the final test shown below.

/** * Testing submitting the password reset page.

*/public function testSubmitPasswordReset(){ $user = factory(User::class)->create([ 'password' => bcrypt(self::USER_ORIGINAL_PASSWORD), ]); $token = Password::broker()->createToken($user); $password = str_random(); $this ->followingRedirects() ->from(route(self::ROUTE_PASSWORD_RESET, [ 'token' => $token, ])) ->post(route(self::ROUTE_PASSWORD_RESET_SUBMIT), [ 'token' => $token, 'email' => $user->email, 'password' => $password, 'password_confirmation' => $password, ]) ->assertSuccessful() ->assertSee(__('passwords.

reset')); $user->refresh(); $this->assertFalse(Hash::check(self::USER_ORIGINAL_PASSWORD, $user->password)); $this->assertTrue(Hash::check($password, $user->password));}In this test we use the Hash facade to assert the user’s password has changed to the given password, thus successfully completing the password reset.

ConclusionThis concludes our testing for Laravel’s password resets.

In ten short tests we were able to do things like create test users and valid password reset tokens, make HTTP requests to our application, assert the response contains desired content, and check if the user’s password has changed as a result of the request.

Laravel has provided ample testing capabilities and I strongly recommend reading the documentation for a deeper look at the possibilities.

You can view the source code for this project on GitHub.

.

. More details

Leave a Reply