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

jrdavidson's avatar

Testing Endpoint and not calling action class

I'm trying to test when an administrator tries to suspend a user that the SuspendAction is run and is redirected. I would instead test specifics of the action class in a separate test however I'm not sure how I can prevent it from getting inside of the actual action class.

<?php

declare(strict_types=1);

namespace App\Http\Controllers\Users;

use App\Actions\Users\SuspendAction;
use App\Exceptions\CannotBeSuspendedException;
use App\Http\Controllers\Controller;
use App\Models\User;

class SuspendController extends Controller
{
    /**
     * Suspend a user.
     *
     * @param  \App\Models\User  $user
     * @return   \Illuminate\Http\RedirectResponse
     */
    public function __invoke(User $user)
    {
        $this->authorize('suspend', $user);

        try {
            SuspendAction::run($user);
        } catch (CannotBeSuspendedException $e) {
            return redirect()->back()->with('error', $e->getMessage());
        }

        return to_route('users.index');
    }
}
beforeEach(function () {
    $this->user = User::factory()->create();
});

test('invoke calls suspend action and redirects', function () {
    $this->actingAs(administrator())
        ->patch(action([SuspendController::class], $this->user))
        ->assertRedirect(action([UsersController::class, 'index']));

    RetireAction::shouldRun()->with($this->user);
});
0 likes
16 replies
Sinnbeck's avatar

Don't make a static call and inject the class. That makes it easy to mock

Sinnbeck's avatar

@jrdavidson like this

    public function __invoke(User $user, SuspendAction $suspend)
    {
        $this->authorize('suspend', $user);

        try {
            $suspend->run($user);
        } catch (CannotBeSuspendedException $e) {
            return redirect()->back()->with('error', $e->getMessage());
        }

        return to_route('users.index');
    }

You can then just swap it with a mock in the container https://laravel.com/docs/9.x/mocking#mocking-objects

Sinnbeck's avatar

Unless it's actually a facade in which case you can just mock it

jrdavidson's avatar

@Sinnbeck My apologies. This is where the action will either return void or an exception. I'm just wanting to make sure it did run.

test('invoke calls suspend action and redirects', function () {
    SuspendAction::mock()->shouldReceive('run')->with($this->user);

    $this->actingAs(administrator())
        ->patch(action([SuspendController::class], $this->user))
        ->assertRedirect(action([UsersController::class, 'index']));

    SuspendAction::shouldRun()->with($this->user);
});
jrdavidson's avatar

@Sinnbeck The problem is that it still says the following error.

No matching handler found for Mockery_2_App_Actions_Users_SuspendAction::run(object(App\Models\User)). Either the method was unexpected or its arguments matched no expected argument list for this method
jrdavidson's avatar

@Sinnbeck What I do realize is calling $action->run($user) then calls the handle method on the action class. So I need to make sure it realizes it gets to the handle method.

jrdavidson's avatar

@Sinnbeck What I'm wanting to do is make sure that there is a call to the run method and that nothing is returned. I don't want it to actually run the action. I"m thinking the shouldRun method is not what I need here.

jrdavidson's avatar

@Sinnbeck WIth this current test I have the output from the test is saying that the call to run was called but no expectations were made..

test('invoke calls suspend action and redirects', function () {
    SuspendAction::mock()->shouldReceive('run')->with($this->user)->andReturn();

    $this->actingAs(administrator())
        ->patch(action([SuspendController::class], $this->user))
        ->assertRedirect(action([UsersController::class, 'index']));
});
Sinnbeck's avatar

@jrdavidson but what about your original version (I moved the mock to before the run

test('invoke calls suspend action and redirects', function () {
    RetireAction::shouldRun()->with($this->user);
    $this->actingAs(administrator())
        ->patch(action([SuspendController::class], $this->user))
        ->assertRedirect(action([UsersController::class, 'index']));

    
});
jrdavidson's avatar

@Sinnbeck I don't understand. There shouldn't be any expectations of what it does. I just want to make sure there's just a call to the run method.

Please or to participate in this conversation.