goldrak's avatar

Test for console command

Hi,

I want make a integration test for this console command with Laravel Prompts

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use function Laravel\Prompts\search;
use function Laravel\Prompts\select;
use function Laravel\Prompts\confirm;
use LdapRecord\Models\OpenLDAP\User as LdapUser;
use App\Models\User;
use App\Models\Role;

class RegisterUser extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:register-user';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /** 
     * Users info  array
     *
     * @var array
     */
    private $usersInfo = [];

    /**
     * Execute the console command.
     */
    public function handle()
    {

        $selection  = search(
            label: 'Search for the user that should receive the mail',
            options: function (string $value) {
                if (strlen($value) > 0) {
                    // Realiza la búsqueda y obtiene los resultados
                    $results = LdapUser::select(['cn', 'mail'])
                        ->whereHas('mail')
                        ->whereContains('mail', $value)
                        ->OrderBy('mail')
                        ->get();

                    $this->usersInfo = [];

                    foreach ($results as $user) {
                        $cn = $user->getFirstAttribute('cn');
                        $mail = $user->getFirstAttribute('mail');

                        $displayString = $cn . ' (' . $mail . ')';
                        $this->usersInfo[$displayString] = ['name' => $cn, 'email' => $mail];
                    }

                    return array_keys($this->usersInfo);

                    return $results->map(function ($user) {
                        return $user->getFirstAttribute('cn') . ' (' . $user->getFirstAttribute('mail') . ')';
                    })->toArray();
                }
                return [];
            },
            validate: function (int|string $value) {

                $user = User::where('email', $this->usersInfo[$value]['email'])->first();

                if ($user) {
                    return 'This user is already registered.';
                }
            }

        );

        if ($selection && isset($this->usersInfo[$selection])) {
            $name = $this->usersInfo[$selection]['name'];
            $email = $this->usersInfo[$selection]['email'];

            $role = select(
                label: '¿Que rol tendrá el usuario?',
                options: [
                    'admin' => 'Administrador',
                    'gestor' => 'Gestor',
                    'cau' => 'CAU'
                ],
                default: 'admin'
            );

            $confirm = confirm(
                label: '¿Esta seguro de crear el usuario ' . $name . ' (' . $email . ') con el ' . $role . '?',
                default: false,
                yes: 'Crear usuario',
                no: 'Cancelar',
            );

            if ($confirm) {
                $user = User::create([
                    'name' => $name,
                    'email' => $email,
                ]);

                $role = Role::where('slug', $role)->first();


                $user->roles()->sync($role);

                $this->info('Usuario creado correctamente');
            } else {
                $this->info('Operación cancelada');
            }
        }
    }
}

I search in documentation

But I don't show to make test for the search component.

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use LdapRecord\Models\OpenLDAP\User;
use LdapRecord\Laravel\Testing\DirectoryEmulator;


class RegisterUserCommandTest extends TestCase
{

    use RefreshDatabase, WithFaker;


    protected function setUp(): void
    {
        parent::setUp();

        $this->seed(\Database\Seeders\PermissionsSeeder::class);
    }


    public function test_add_admin(): void
    {

        $fake = DirectoryEmulator::setup('default');

        $userData = [
            'cn' => $this->faker->name,
            'mail' => $this->faker->email,
            'guid' => $this->faker->uuid,
        ];

        $ldapUser = User::create($userData);

        $this->artisan('app:register-user');
    }
}

Thanks for the help

0 likes
7 replies
goldrak's avatar

Hello @s4muel I have reviewed the link that was sent to me and I see that it tests each type of component and with pest instead of PHPunit.

I can study changing the tests to a project but it would be very laborious due to the work already done.

I launch the command through artisan in the tests so I have to use the expect that I have available and in this case it would be the expectsQuestion if I have read the code correctly.


        $this->artisan('app:register-user')
        ->expectsQuestion('Search for the user that should receive the mail', 'Test Name ([email protected])');

But it gives me the following error.

  ─────────────────────────────────────────────────────────────────────────────────────────────────────  
   FAILED  Tests\Feature\RegisterUserCommandTest > add admin                            LogicException   
  Choice question must have at least 1 choice available.

  at vendor/symfony/console/Question/ChoiceQuestion.php:36
     32▕      */
     33▕     public function __construct(string $question, array $choices, mixed $default = null)
     34▕     {
     35▕         if (!$choices) {
  ➜  36▕             throw new \LogicException('Choice question must have at least 1 choice available.');
     37▕         }
     38▕ 
     39▕         parent::__construct($question, $default);
     40▕ 

      +11 vendor frames 
  12  app/Console/Commands/RegisterUser.php:42
      +13 vendor frames 
  26  tests/Feature/RegisterUserCommandTest.php:45

Thanks for the time.

goldrak's avatar

Hello again, I finally got it to work for me, I had to change part of the functionality of the command due to the integration with LDAP and how search works so that both the command and the tests work, but it already works for me.


    public function test_add_admin(): void
    {

        $fake = DirectoryEmulator::setup('default');

        $userData = [
            'cn' => 'Test Name',
            'mail' => '[email protected]',
            'guid' => $this->faker->uuid,
        ];

        $ldapUser = User::create($userData);

        $this->artisan('app:register-user')
            ->expectsQuestion('Busca el usuario por email:', '[email protected]')
            ->expectsChoice('¿Que rol tendrá el usuario?', 'Administrador', ['Administrador', 'CAU', 'Gestor', 'admin', 'cau', 'gestor'])
            ->expectsConfirmation('¿Esta seguro de crear el usuario Test Name ([email protected]) con el rol Administrador?', 'yes')
            ->assertExitCode(0);

        $this->assertDatabaseHas('users', [
            'name' => 'Test Name',
            'email' => '[email protected]',
        ]);

        $this->assertDatabaseHas('users_roles', [
            'role_id' => 1,
            'user_id' => 1,
        ]);
    }

Thank you so much.

goldrak's avatar

Well let's go with the last test.

Now all the tests I needed work for me except the last one that I am validating that the search warns that the user is already registered.

Do you know if there is a way to simulate a ctrl+c or a way to stop the artisan command by code in a test?

    public function test_try_create_same_user(): void
    {

        $fake = DirectoryEmulator::setup('default');

        $userData = [
            'cn' => 'Test Name',
            'mail' => '[email protected]',
            'guid' => $this->faker->uuid,
        ];

        $ldapUser = User::create($userData);

        $user = UserModel::create([
            'name' => 'Test Name',
            'email' => '[email protected]',
        ]);

        $role = Role::where('slug', 'admin')->first();

        $user->roles()->sync($role);

        $this->artisan('app:register-user')
            ->expectsQuestion('Busca el usuario por email:', '[email protected]')
            ->expectsOutput('El usuario ya existe');

		//Not finish
    }

Thank you.

s4muel's avatar

@goldrak could be, but nothing i know of. since the command validates the input and waits for the user to enter unique email, i would just simply enter valid email address right after checking the existing one

 $this->artisan('app:register-user')
            ->expectsQuestion('Busca el usuario por email:', '[email protected]')
            ->expectsOutput('El usuario ya existe')
            ->expectsQuestion('Busca el usuario por email:', '[email protected]')
            ->assertExitCode(0);

something like this. not ideal, but simple enough and i would not mind doing something extra in the test in this case.

Please or to participate in this conversation.