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());
}
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'm trying to send e-mails to new users. I made changes in my env. file. I am using a gmail.com mail service. I want to send e-mails to users including their name. Ex:
Hi John, your registration is succesful!
Here John part will be user name.
my code is here in RegistrationController:
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
$to_name = $data['name'];
$to_email= $data['email'];
$body =[];
$mailData=array('body'=>$body);
Mail::send('email.email-register', $mailData, function($message) use ($to_name, $to_email ){
$message->to($to_email, $to_name)->subject('Registration is succesfull!');
$message->from(env('MAIL_USERNAME'));
});
return $user;
}
Also i am using a mail template. I want to send user as variable to the view. How do i do that?
1- You need to run command php artisan make:mail RegisterMail
2- Add Library use App\Mail\RegisterMail;
Mail::to('YourEmail#gmail.com')->send(new RegisterMail($mailData));
3- Create a new Data Member in your App\Mail\RegisterMail.php Class
public $mailData;
public function __construct($mailData)
{
$this->mailData = $mailData;
}
4- Add this in your App\Mail\RegisterMail.php
public function build(){
return $this->from('youremail#gmail.com','your name')->replyTo('youremail#gmail.com')->subject($this->mailData['subject'])->view('email')->with('mailData',$this->mailData);
}
I am new to unit testing. I want to authenticate a user without using Factory. I want my testing code to be simple. I don't know how to use the Factory. Here is my code :
public function loginVerify()
{
$user = factory('App\User')->create();
}
the first thing that you have to do is follow the naming convention
Change
public function loginVerify()
{
$user = factory('App\User')->create();
}
to
public function testLoginVerify()
{
$user = factory('App\User')->create();
}
always use the test as a prefix for your testing function name.
and now as we look at your question, you can simply do this...
public function testLoginVerify()
{
$user_details = [
'email' => 'demo#gmail.com', // the email of a particular user
'password' => 'password' // the password of that user
];
$this->post('/login', $user_details)->assertRedirect('/home');
}
This is the very simplest way to do this.
I am building a user invitation system with Laravel that does not work I have the error here email invitation after sending the email to the user the descriptive image is lower Click here to activate!
my InviteController
public function process(Request $request)
{
// validate the incoming request data
do {
//generate a random string using Laravel's str_random helper
$token = str_random();
} //check if the token already exists and if it does, try again
while (Invite::where('token', $token)->first());
//create a new invite record
$invite = Invite::create([
'email' => $request->get('email'),
'token' => $token
]);
// send the email
Mail::to($request->get('email'))->send(new InviteCreated($invite));
// redirect back where we came from
return redirect()
->back();
}
public function accept($token)
{
// Look up the invite
if (!$invite = Invite::where('token', $token)->first()) {
//if the invite doesn't exist do something more graceful than this
abort(404);
}
// create the user with the details from the invite
User::create(['email' => $invite->email]);
// delete the invite so it can't be used again
$invite->delete();
// here you would probably log the user in and show them the dashboard, but we'll just prove it worked
return 'Good job! Invite accepted!';
}
my image error
Do a dd of $invite before passing to the mail to make sure you have the records:
//create a new invite record
$invite = Invite::create([
'email' => $request->get('email'),
'token' => $token
]);
dd($invite);
// send the email
Mail::to($request->get('email'))->send(new InviteCreated($invite));
Then on InviteCreated make sure you store $invite on a public property so its available on the blade template:
class InviteCreated extends Mailable{
public $invite;
public function __construct($invite){
$this->invite = $invite;
}
...... if you use markdown then pass the `$invite`..
public function build()
{
return $this
->markdown(your markdown mail')
->with([
'invite'=>$this->invite
]);
}
}
I am using ci-merchant library and integrated it succesfully and also works for paypal account owner user.But dont know how to processs for user who dont have paypal acc and wants to pay via credit or debit card on my website only*(without redirect to paypal)* any idea????abt that.....this is the code i use for the normal paypal payment in my controller and works good as well..
$this->load->library('merchant');
$this->merchant->load('paypal_express');
$settings = $this->merchant->default_settings();
$settings = array(
'username' => 'takeout_api1.rest.com',
'password' => '1369227981',
'signature' => 'AnOQDpMvzNQqHN5u7vb9BKLaKYLoALq6R0g3ohOwD4RQgO0DQDI5l7V4',
'test_mode' => true,
);
$this->merchant->initialize($settings);
$params = array(
'amount' => 1500.00,
'currency' => 'CAD',
'return_url' => 'http://192.168.1.7/takeout/order_detail/test',
'cancel_url' => 'http://192.168.1.7/takeout/order_detail/test');
$response = $this->merchant->purchase($params);
function test()
{
$settings = array(
'username' => 'takeout_api1.rest.com',
'password' => '1369227981',
'signature' => 'AnOQDpMvzNQqHN5u7vb9BKLaKYLoALq6R0g3ohOwD4RQgO0DQDI5l7V4',
'test_mode' => true);
$this->merchant->initialize($settings);
$params = array(
'amount' => 1500.00,
'currency' => 'CAD',
'return_url' => 'http://192.168.1.7/takeout/order_detail/test',
'cancel_url' => 'http://192.168.1.7/takeout/order_detail/test');
$response = $this->merchant->purchase_return($params);
if ($response->success())
{
// mark order as complete
echo "yo";
exit;
}
else
{
$message = $response->message();
echo('Error processing payment: ' . $message);
exit;
}
}
You could interface your Merchant services
interface merchantServiceInterface
{
public function initialize();
public function purchase();
public function purchase_return();
}
Paypal
class Paypal implements merchantServiceInterface
{
public function initialize(){}
public function purchase(){}
public function purchase_return(){}
}
Credit/Debit Cards
class Realex implements merchantServiceInterface
{
public function initialize(){}
public function purchase(){}
public function purchase_return(){}
}
Now in your form, have a little radio button group and ask the user to select
either paypal or credit/debit card
<label>Choose a payment Method</label>
<label>Paypal<label>
<input type="radio" name="merchant" value="paypal" />
<label>Credit/Debit Card<label>
<input type="radio" name="merchant" value="debit" />
Merchant Library
class Merchant
{
protected $_service;
public function __construct(merchantServiceInterface $service)
{
$this->_service = $service;
}
public function initialize()
{
// Will either run Paypal/Realex initialize()
// Depending on what instance was passed to _service
//
$this->_service->initialize();
}
}
Controller
class Controller extends CI_Controller
{
public function method()
{
if($this->input->post('merchant') == 'paypal')
{
$service = new Paypal();
}
elseif($this->input->post('merchant') == 'debit')
{
$service = new Realex();
}
$this->load->library('Merchant', $service);
$this->merchant->initialize();
}
}
Edit to answer your comment
I just used Realex as an example
You need to figure out what both libraries have in common, or at a very low level abstraction figure out what they share.
an example would be
They both need a initialize method to configure options
They both need to send a request to an API
They both need a responce
etc etc keep abstracting away
How you handle these, will be unique to the library itself.
interface merchantServiceInterface
{
// Use the facade design pattern here
// so configuration is done in each library
public function initialize();
// Send a request with data
// Paypal - use http_build_query and curl
// Realex - use xml and curl
public function request(array $data);
public function responce();
}
Paypal Express Checkout doesn't support taking credit cards on your site. It is an off-site gateway, so redirect is mandatory.
You need to explore using PayPal Pro, Payflow, or any number of other gateways which support accepting credit cards directly on your website (plus the extra PCI requirements which come with that).