Laravel tests are not saving factories to database - laravel

I'm having trouble with testing in that the records of my factories are not being saved to the database for my API tests. Here is an example test
use DatabaseTransactions;
public function testUserMe()
{
$user = factory(\App\Models\User::class, 'userA')->create();
$token = factory(\App\Models\ApiToken::class, 'userA-token')->create();
$this->refreshApplication(); // I've tried with and without this line
$headers = ['HTTP_Authorization' => 'Bearer '. $token->token];
$this->json('GET', '/user/me', [], $headers, $headers)
->assertJson([
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'email' => $user->email,
])
->assertStatus(200);
}
Note: I have $headers in the request twice as I'm seeing inconsistencies in examples but this is irrelevant to the issues.
The issue I'm having is that the middleware is failing as the user and token have not been committed to the database. If I remove the use DatabaseTransactions it will work as expected (middleware works and tests pass) but the data is not cleared after the test.
I have the exact same thing working on a previous project with what I think is the same setup, including the lack of use of setup() and teardown(). Does anyone have some ideas on where else I can look?
Why would use DatabaseTransactions be preventing the records being created?

Related

How to test Auth::once() in Laravel?

I'm using Auth:once() to authenticate users for a single request.
if (!Auth::once($this->only('email', 'password'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
// we store the user + password for one request in the session
// so we can use it in the next request
$this->session()->put('email', $this->email);
$this->session()->put('password', $this->password);
But the test user is permanently authenticated, and assertGuest() is always false, not just for a single request. This is happening only in the test!
$user = User::factory()->create();
$response = $this->post('/api/login', [
'email' => $user->email,
'password' =>' password',
]);
$response = $this->get('/api/home');
$this->assertGuest();
I tested manually from Postman, and everything seems to be okay. So I think Laravel considers it a single request for the entire test.

Laravel Passport: How can I properly setup a feature test?

I am trying to create a Feature test that tests getting an OAuth access token. When using Postman, everything works great (actual code) so I am confident it is in my test code I am not implementing the setup correctly.
In the past, I have run the artisan command, and simply copy/paste the client_secret into my .env file. Because I am using CI/CD, I cannot do that. I need to either:
create an artisan command to append the .env
read the secret from the oauth_clients table (seems the easiest route)
I've followed this SO thread to help get where I'm at, but not able to make it work.
Here is what my test looks like:
MyFeatureTest.php
use RefreshDatabase, WithFaker, DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->artisan('passport:install');
}
public function a_user_can_get_a_token()
{
$credentials = [
'username' => 'username',
'password' => 'password',
];
$http = $this->postJson('api/v1/login', $credentials);
$response = json_decode($http->getContent());
// dd($response);
$http->assertStatus(200)
->assertJsonStructure([
'token_type', 'expires_in', 'access_token', 'refresh_token',
])
->assertJson([
'token_type' => $response->token_type,
'expires_in' => $response->expires_in,
'access_token' => $response->access_token,
'refresh_token' => $response->refresh_token,
]);
In my Controller that handles the login route:
public function __construct()
{
$this->client = DB::table('oauth_clients')
->where('id', 2)
->first();
}
...
public function getOauthAccessToken($credentials)
{
$response = Http::asForm()->post(env('APP_URL') . '/oauth/token', [
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'username' => $credentials['username'],
'password' => $credentials['password'],
'scope' => '*',
]);
if ($response->successful()) {
return $response->json();
} else {
dd($response->body());
}
}
When using postman to test my routes/controller, everything works great. I get a Barer token back as expected.
When I run my Feature test, my controller returns null.
Trying to troubleshoot, I've tried dd($response->body()) above. Here is what I am getting:
"{"error":"invalid_client","error_description":"Client authentication failed","message":"Client authentication failed"}"
If I dd($this->client->secret), I am able to see the key. This is super confusing as it looks like everything is working...
How can I properly set up my test so that passport is ready to go/configured when I hit the login test(s)? Thank you for any suggestions!
Thank you everyone for your suggestions!
This is what ended up working for me. Since I am the only consumer of my api, I don't need to return the entire /oauth/token response (although it might be helpful in the future).
MyTest.php
use RefreshDatabase, WithFaker, DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->artisan('passport:install', ['--no-interaction' => true,]);
}
/** #test */
public function a_user_can_authenticate_using_email()
{
$credentials = [
'username' => 'test#email.com',
'password' => 'password',
];
$http = $this->postJson('api/v1/login', $credentials)
->assertStatus(200);
}
MyController.php
// authenticate the $credentials to get a $user object.
...
return $this->getOauthAccessToken($user);
...
private function getOauthAccessToken($user)
{
$token = $user->createToken('My Personal Access Token')->accessToken;
return response()->json($token);
}
Instead of validating I get the exact json back (would be ideal) I'm just verifying that I'm getting a 200 response back. Because my authentication is somewhat cumbersome (checking multiple db's) it was important to make sure that I was getting through everything with a 200.
Hope this helps someone!

Laravel: Why am I getting a 401 response from Passport in my feature test?

I am unsuccessfully trying to test authentication using Passport.
Here is my feature test:
public function a_user_can_authenticate_using_email() {
// Install passport.
$this->artisan('passport:install', ['--no-interaction' => true]);
// Create a user.
$user = factory(User::class)->create([
'username' => 'test#example.com',
'password' => 'password',
]);
// Get the id & secret.
$client = DB::table('oauth_clients')
->where('password_client', true)
->first();
// dd('id: ' . $client->id); // works great
// dd('secret: ' . $client->secret); // works great
// dd('username: ' . $credentials['username']); // works great
// dd('password: ' . $credentials['password']); // works great
$response = Http::asForm()->post(env('APP_URL') . '/oauth/token', [
'grant_type' => 'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => $credentials['username'],
'password' => $credentials['password'],
'scope' => '*',
]);
dd($response->status()); // 401
}
If I dump the response body, I am seeing an invalid client:
dd($response->body());
"{"error":"invalid_client","error_description":"Client authentication failed","message":"Client authentication failed"}"
I don't understand why I am seeing this error in my test.
If I use Postman to test my application, everything works great so I'm confident this is something specific to my test.
I am using sqlite memory database.
If I hardcode these values in my .env or just right in the test block, everything works great as well. How can I properly get the values from the database in my test? Thank you for any suggestions!
You'll have to run $user->save() so the user is saved to the database before using it to log in.

Skip Laravel's FormRequest Validation

I've recently added HaveIBeenPwned to my form request class to check for cracked passwords. Given that this makes an external API call, is there a way for me to skip either this validation rule or the FormRequest class altogether during testing?
Here's the request I make in my test.
$params = [
'first_name' => $this->faker->firstName(),
'last_name' => $this->faker->lastName(),
'email' => $email,
'password' => '$password',
'password_confirmation' => '$password',
'terms' => true,
'invitation' => $invitation->token
];
$response = $this->json('POST', '/register-invited', $params);
The functionality I'm testing resides on a controller. In my test I POST an array of data that passes through a FormRequest with the following rules.
public function rules()
{
return [
'first_name' => 'required|string|max:70',
'last_name' => 'required|string|max:70',
'email' =>
'required|email|unique:users,email|max:255|exists:invitations,email',
'password' => 'required|string|min:8|pwned|confirmed',
'is_trial_user' => 'nullable|boolean',
'terms' => 'required|boolean|accepted',
];
}
I want to override the 'pwned' rule on the password so I can just get to the controller without having to worry about passing validation.
With the information provided I'd say you are executing an integration test which does an actual web request. In such a context I'd say it's fine for your test suite to connect to a 3rd party since that's part of 'integrating'.
In case you still prefer to mock the validation rule you could swap out the Validator using either the swap
$mock = Mockery::mock(Validator::class);
$mock->shouldReceive('some-method')->andReturn('some-result');
Validator::swap($mock);
Or by replacing its instance in the service container
$mock = Mockery::mock(Validator::class);
$mock->shouldReceive('some-method')->andReturn('some-result');
App:bind($mock);
Alternatively you could mock the Cache::remember() call which is an interal part of the Pwned validation rule itself. Which would result into something like
Cache::shouldReceive('remember')
->once()
->andReturn(new \Illuminate\Support\Collection([]));

Laravel exceptions not working at all. It return phpinfo.php in every case

Hi guys i'm working with laravel 5.6, i have enabled debugging in .env but still it is not returning any exception and error.
instead of any error or exception it shows a complete page of phpinfo.php
here is an example image what i am actually getting
https://www.hostinger.com/tutorials/wp-content/uploads/sites/2/2017/03/inside-the-php-info-section.png
Let me show you my code
public function store(Request $request)
{
$request->validate([
'first_name' => 'required|min:3',
'last_name' => 'required|min:3',
]);
...
}
the desired output was that if i have not entered any field i.e first_name or last_name it should provide an error that first_name or last_name is required but instead of this it return complete phpinfo.php page
Although the code sample you have provided does not have any error
I think you should try using the validation like this
$this->validate($request, [
'first_name' => 'required|min:3',
'last_name' => 'required|min:3',
]);
If you still face the error please try commenting you validation code for instance or using a dd() after validation so that we could ensure that the error is in the validation part or somewhere else.

Resources