Authenticate user without using factory in unit testing - laravel

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.

Related

Hash password after Validator::make

I want to hash the password after validating the data.
My code:
public function create(Request $request){
$data = Validator::make($request->only(['name', 'email', 'password']), [
'name' => 'required|min:3:max:20',
'email' => 'required|email|unique:users',
'password' => 'required|min:6',
]);
if ($data->fails()) {
//Do something
}else{
User::create($data);
}
}
So how to hash the password after validation?
I tried to override the password inside $data, But it's not working
$data->safe()->only('password') = Hash::make($data->safe()->only('password'));
I can't use $request['password'] as I won't be able to validate it and check if it's empty ..etc.
An alternative approach would be to use an Eloquent Mutator to automatically hash the password field when it is set.
// User Model
public function setPasswordAttribute($value): void
{
$this->attributes['password'] = Hash::make($value);
}
I personally like this approach because you won't have to worry about it in the controllers. You can just set it once and forget about it. :)
The quick answer to your question would be to use the Eloquent make function.
$user = User::make($data);
$user->password = Hash::make($password);
$user->save();
Where $password is where ever you have the password stored. In your case:
$password = $data->safe()->only('password')
There may be a more efficient way, based on your exact intent. In general, the above solution will work.
The make function creates an Eloquent model, but does not store it in the database. That's why you can edit the values and then call $user->save()
Use a mutator method to set the password. Override the method by adding:
public function setPasswordAttribute($value)
{
$this->attributes['password'] = 'some random password generator';
}
there is document:
https://laravel.com/docs/8.x/eloquent-mutators#defining-a-mutator

How to test a controller with model injection but without middleware?

When I test this:
use WithoutMiddleware;
public function testPutSportOK()
{
$sport = Sport::first();
$sportName = 'Modification '.$sport->sport_name;
$position = random_int(0,100);
$post = [
'sport_name' => $sportName,
'position' => $position
];
$response = $this->json('PUT', '/api/sports/'.$sport->id, $post);
$response->assertStatus(200);
The test failed because I use the model injection in my controller. I understand that this injection needs the "bindings" middleware. But as I disabled all the middlewares, this injection cannot be done.
I disabled the middlewares for authentication reasons.
I tried to add this:
$this->withMiddleware('bindings');
But it's still the same.
How to test a controller using the model injection and without middlewares?
Edit
Add the controller with the model injection:
public function update(Request $request, Sport $sport)
{
// var_dump($sport);
$validator = Validator::make($request->all(), [
'sport_name' => 'required',
'position' => 'required|int'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
try {
// not necessary with the injection model
// $sport = Sport::findOrFail($id);
$sport->fill($request->all());
$sport->save();
return new SportResource($sport);
} catch (\Exception $ex) {
return response()->json($ex->getMessage(), 400);
}
}
From the source code here, you can actually disable "some" of your middlewares (instead of disabling them all) by giving an array of middlewares you WANT to disable.
$this->withoutMiddleware([
\App\Http\Middleware\Authenticate,
\App\Http\Middleware\RedirectIfAuthenticated,
// Add more here
]);
Finally I gave up the idea of ​​model injection. To prefer the passage of a classic id between the route and the controller.
As this my PHPUnit + Postman tests work perfectly and I'm not bored with this "binding" middleware any more.
Certainly it requires to write one more line in the controller to read the corresponding model in database. But it's only one line. I accept it !

how to getting the email verification link in Unit Test Laravel

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());
}

Laravel Testing - Should I be creating dependant resources in each test?

Starting on a new build with Laravel Spark 6 (Laravel 5.6) and decided to give TDD a try.
First test was lovely, I created a unit test to make sure that users can create an team.
(Pseudo code):
class AddNewTeamTest extends TestCase
{
/** #test */
public function admin_can_create_new_team()
{
// Create a user account
$data = [
// Information for tea,
];
$response = $this->withHeaders([
'X-Requested-With' => 'XMLHttpRequest',
])
->actingAs($user)
->json('POST', '/api/teams', $data);
$response
->assertStatus(201);
}
}
Using this in TDD style was a nice process, but now I want to be able to write a test for adding a member to that team.
It seems backwards that in this new test, I would run all of the code in my first test. Is there anyway around this? For the new test I would need a user and team already created before I could test adding a user to that team..
Any links or advice welcome!
You can use function setUp() and build your enviroment inside it.
So your class should looks like that:
class AddNewTeamTest extends TestCase
{
protected function setUp()
{
// Create a user account
// Create your enviroment, etc.
$this->actingAs($user)
}
/** #test */
public function admin_can_create_new_team()
{
$data = [
// Information for tea,
];
$response = $this->withHeaders([
'X-Requested-With' => 'XMLHttpRequest',
])
->json('POST', '/api/teams', $data);
$response
->assertStatus(201);
}
public function testAnother()
{
\\your next test
}
}
If you need a team in next few cases, that should be added in setUp().
Also, you can make your next test needed ypur previous one. In that case you can return something in admin_can_create_new_team() and take as parameter in testAnother()
More info:
https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.test-dependencies

Disable hashing on Auth::attempt

I am working with an old database without hashed passwords, also this database needs to be unhashed since it is connected to a Runnable JAR.
I did everything to connect it with Laravel 5.3 and it worked, but.. When comes to login it always return false.
Here is the function code:
public function login(Request $request)
{
$this->validate($request, [
'account' => 'required|alpha_num|exists:accounts,account',
'password' => 'required|alpha_num|min:4',
]);
if(Auth::attempt(['account' => $request->account, 'password' => $request->password])){
return redirect()->route('account');
}
return redirect()->back()->withInput();
}
I came to the conclusion that Auth::attempt hashes the given password through the view and when comparing to the unhashed one in the database, returns false.
How can i fix this??
Thank you.
You will need to use another method of manual authentication.
$user = User::where('account', $request->account)
->where('password', $request->password)
->first();
if($user) {
Auth::loginUsingId($user->id);
// -- OR -- //
Auth::login($user);
return redirect()->route('account');
} else {
return redirect()->back()->withInput();
}
You just can add this to your App/User.
If you are using another driver in config/hashing.php - change bcrypt to argon/argon2i
public function getAuthPassword() {
return bcrypt($this->password);
}

Resources