Summer Sale! All accounts are 50% off this week.

alberto98's avatar

How to pass the same user to all the feature test/methods

Hi everybody,

I have a question.

Let's say I have a feature test that tests a CRUD. The Controller that will be tested needs a User to be logged.

class CategoryTest extends TestCase
{
    public function test_category_creation(): int
    {
        Sanctum::actingAs(User::factory()->create());
        $response = $this->put('/api/category',[
            'name' => 'Test of a category',
            'description' => 'Testing category',
            'notes' => 'This is a note',
            'created_by' => 1
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);
        $findId = json_decode($response->getContent());
        return $findId->id;
    }

    /**
     * @depends test_category_creation
     */
    public function test_category_update(int $id)
    {
        $response = $this->patch('/api/category/'.$id,[
            'name' => 'Test update category',
            'description' => 'This is an updated description'
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);
    }
}

If I'll put another Sanctum::actingAs(User::factory()->create()); in the test_category_update, will it create another User different from the one already used inside the test_category_creation? If yes, is there a way so I can pass the user_id to all the methods in my CategoryTest (or even bettere, in all my feature tests)?

0 likes
7 replies
Sofia's avatar

You can test without middleware if the only reason the user is needed is auth. If you're referencing the user in the code you're trying to test, then you will need one in the database and yes, to answer your question, a new one is created for each test. Here's now to ignore middleware:

$this->withoutMiddleware([
   App\Http\Middleware\Authenticate::class, //no auth user
   Illuminate\Auth\Middleware\Authorize::class, //ignores route gates
]);

Not sure how to go about using the same user for all requests, it would depend on how you have your test db/migrations set up.

alberto98's avatar

@Sofia Unfortunately I don't need to skip the middleware, I need to have the same user who created the category to update the same category because another user can't update it.

martinbean's avatar

@alberto98 Your test cases should all be self-contained. If a test case requires a user, then create and authenticate a user.

alberto98's avatar

@martinbean But I need the same user so he can update the category that was previously created by him. Another user can't update the category he just created.

martinbean's avatar
Level 80

But I need the same user so he can update the category that was previously created by him. Another user can't update the category he just created.

@alberto98 Nope. The test should be separate. You shouldn’t be using state from one test case in another.

If you want to test a user can update a category they’ve created, and also test they can’t update categories created by another users, then create test cases for those and use database factories to create the relevant models:

public function testUserCanUpdateTheirOwnCategories(): void
{
    $user = User::factory()->create();
    $category = Category::factory()->for($user)->create();

    $this
        ->actingAs($user, 'sanctum')
        ->patch("/api/categories/{$id}", [
            'name' => 'Test update category',
            'description' => 'This is an updated description',
        ])
        ->assertOk()
        ->assertJson(['status' => 'success']);

    // Assert category was updated in database
    $this->assertDatabaseHas('categories', [
        'id' => $category->getKey(),
        'name' => 'Test update category',
        'description' => 'This is an updated description',
    ]);
}

public function testUserCannotUpdateCategoryCreatedByAnotherUser(): void
{
    $user = User::factory()->create();
    $otherUser = User::factory()->create();
    $category = Category::factory()->for($otherUser)->create();

    $this
        ->actingAs($user, 'sanctum')
        ->patch("/api/categories/{$id}", [
            'name' => 'Test update category',
            'description' => 'This is an updated description',
        ])
        ->assertForbidden();

    // Assert category was not updated in database
    $this->assertDatabaseMissing('categories', [
        'id' => $category->getKey(),
        'name' => 'Test update category',
        'description' => 'This is an updated description',
    ]);
}

Also, status => success in your JSON response is completely redundant as you should be using the appropriate HTTP status codes to designate whether a request was “successful” or not.

1 like
alberto98's avatar

@martinbean Okay, this is enlightening!

I'll give it a try when I'm back home.

Thank you so much!

1 like
MohamedTammam's avatar

Every test method will run separately, which means before every test the database will be refreshed and the variable will be re-initialized to the default. If you want to run some code before every test method use setUp method.

class CategoryTest extends TestCase
{
	public $user = null;
	public $categories = [];
	public function setUp() {
		parent::setUp();
		
		// Create a use and categories objects to be available in all other test methods.
		$this->user = User::factory()->create();
		$this->categories = Category::factory()->count(3)->create(['user_id' => $this->user->id]);
	}

	public function test_your_test_method() {
		$this->actingAs($this->user);
	}
}

What that approach you will have the user and categories object in all test cases, but keep in mind that they will be recreated for every test.

Please or to participate in this conversation.