laravel phpunit testing expecting status code 201 but received 302? - laravel

I have a simple PostController with a store method that receives data, validates it, stores it and then redirects to /posts with a success message.
I have written a test to ensure that the post is created and saved as follows:
/** test */
public function testStore()
{
$user = factory('App\User')->create();
$post = factory('App\Post')->create(['user_id' => $user->id]);
$response = $this->actingAs($user)
->json('POST', '/notice', $post->toArray())
->assertStatus(201)
->assertJson([
'created' => true,
]);
}
But I am receiving 301(redirection) instead of 201. I do not understand this.

Related

How with breeze to make feature test for forgot-password?

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

How to structure HTTP test for Laravel relationship?

Using the blog scenario, I have a Post and an Author model. There is a many-to-many relationship with additional attributes on the relationship. In the application, the pivot attributes are saved using
$post->author()->save($author, ['review' => 'Pending']);
How do I format that type of request in a test?
$post = Post::factory()->create();
$author = Author::factory()->create();
$response = $this->actingAs($this->user_update)->patch(**request data**);
I'd like to have a test for each type of user.
When writing tests for an endpoint, you should mostly be testing how it responds to different types of data. For example:
If I send a request with a valid body or parameters, I expect to receive a status code 200 and maybe some data depending on your use case.
If I send a request while being unauthenticated, I expect to receive a status code of 401.
If I send invalid data, I expect to receive a status code of 422 and some error messages for the invalid fields.
If the entity I'm trying to fetch/update/delete does not exits, I expect to
receive get a status code of 404.
With status code 200, or as I like to call them "happy cases", if we can easily identify a new/updated record, it doesn't hurt to test it's working correctly. The majority of testing for the business logic should happen on the service layer.
public function testPostCanBeCreatedForAuthor() {
// arrange
$user = User::factory()->create();
$author = Author::factory()->create(['user_id' => $user->id]);
// act
$response = self::actingAs($user)->postJson('/api/posts', [
'title' => 'A very good title',
'content' => 'Lorem ipsum dolor...'
]);
// assert
$response->assertOk();
$post = Post::where('author_id', $author->id)->first();
self::assertNotNull($post);
self::assertSame('A very good title', $post->title);
// ...
}
public function testPostUpdateRespondsNotFoundWithInvalidPostId() {
$user = User::factory()->create();
$response = $this->actingAs($user)->patchJson('/api/posts/invalid-post-id', [
'title' => 'A very good title',
'content' => 'Lorem ipsum dolor...'
]);
$response->assertNotFound();
}
Edit:
If you want to test the pivot table values, do this:
// App\Models\Post
public function author() {
return $this->belongsToMany(Author::class)->withPivot('review');
}
// ...
// In your test
self::assertSame('Pending', $post->author()->first()->pivot->review);

Nova Laravel Unit Testing Expecting Return 201, But Return 403

I'm gonna make a Unit Testing for my resource.
Here my testing function below :
public function testCreateMyResource()
{
$user = factory(\App\User::class)->states('admin')->create();
$this->actingAs($user);
$data = [
'Field' => "Example",
];
$response = $this->actingAs($user)->postJson('/nova-api/my-resource?editing=true&editMode=create',$data);
$response->assertStatus(201);
$response->assertJson(['status' => true]);
$response->assertJson(['message' => "Created!"]);
}
But it was return 403.
I expected to return 201 as I login normally to Nova dashboard and create new record in the form.
It seems like forbidden to access the route inside Testing Class.
Is there anyway to access the route ? Please any body help me to solve this.

Authorise a user with Laravel Passport when testing RESTful controllers update method

Every time I run the test, I'm getting a 403 response status, what am I doing wrong in here?
I have tried to remove Passport authorization from the test, but then I'm getting a redirect to a login page, 302 status response.
//PostTest:
public function test_can_update_post()
{
//creating a user
$user = factory(User::class)->create();
//creating an author for post
$author = factory(Author::class)->create([
'user_id' => $user->id,
]);
//creating a post
$post = factory(Post::class)->create([
'author_id' => $author->id,
]);
$data = [
'title' => $this->faker->title,
'content' => $this->faker->paragraph,
];
//authorizing user
//I have tried to remove this line, then I'm gettig a redirect to login page 302
$user = Passport::actingAs($user);
$this->actingAs($user)
->patch(route('posts.update', $post->id), $data)
->assertStatus(200);// why I'm getting 403???
}
//API route:
Route::patch('posts/{post}')
->uses('PostController#update')
->middleware('auth:api')
->name('posts.update');
//PostController update method:
public function update(PostUpdateRequest $request, Post $post)
{
$this->authorize('update', $post);
$post->title = $request->input('title');
$post->content = $request->input('content');
$post->save();
return new PostResource($post);
}
//PostPolocy
public function update(User $user, Post $post)
{
return Author::where('user_id', $user->id)->first()->id === $post->author_id;
}
I expect response status 200
I have changed the line in PostPolicy update method to:
if(!$user->author) {
return false;
}
return $user->author->id == $post->author_id;
This worked for me.

How can I use Laravel Validation in the API, it's returning my view now?

I have this route in route/api.php:
Route::post('/register', 'LoginController#register');
My LoginController
class LoginController extends Controller {
public function register(Request $request) {
$this->validate($request, // <-- using this will return the view
// from **web.php** instead of the expected json response.
[
'email' => 'required|email',
'firstName' => 'required|alpha_dash',
'lastName' => 'required',
'password' => 'required|confirmed',
]);
$input = $request->all();
//$plain_password = $input['password'];
$input['uuid'] = uuid();
$input['password'] = Hash::make($input['password']);
$user = User::create($input);
dd($errors);
$response['succes'] = true;
$response['user'] = $user;
return response($response);
}
}
Why does adding a validation call change the behaviour to returning my view / the wrong route. I want the api to validate my request too, not just my "frontend".
When you use Laravel's validate method from controller it automatically handles/takes the step if the validation fails. So, depending on the required content type/request type, it determines whether to redirect back or to a given url or sending a json response. Ultimately, something like thos following happens when your validation fails:
protected function buildFailedValidationResponse(Request $request, array $errors)
{
if ($request->expectsJson()) {
return new JsonResponse($errors, 422);
}
return redirect()->to($this->getRedirectUrl())
->withInput($request->input())
->withErrors($errors, $this->errorBag());
}
So, if the first if statement is true then you'll get a json response and it'll be true if you either send an ajax request or if you attach a accept header with your request to accept json response (When requesting from a remote server). So, make sure your request fulfills the requirements.
Alternatively, you can manually validate the request using the Validator component and return a json response explicitly if it fails.

Resources