How to test Symfony Process inside a queued job?

Published 1 year ago by swalker

Hello there,

I am relatively new to testing and so... I have this inside a queued job:

    public function handle()
    {
        ...

        $output .= $this->runCommand("ls -la");
        ...
    }
    /**
     * Runs a command
     *
     * @param        $cmd
     * @param string $cwd
     *
     * @return string
     */
    private function runCommand($cmd, $cwd = '')
    {
        
        $process = new Process(trim($cmd), $cwd);
        $process->setTimeout(300);
        $process->run();
        $process->wait();
        
        if ( ! $process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }
        
        return trim($process->getOutput());
    }

How can I test this without actually running the command?

Is it ok if I mock the Proccess class inside my code?

Best Answer (As Selected By swalker)
swalker

For this test I made a mock of Process inside the job

    private function getProccess($cmd,$cwd)
    {
        if (app()->environment() != 'testing'){
            return new Process(trim($cmd), $cwd);
        }
        
        $process = \Mockery::mock(Process::class);
        $process->shouldReceive(
            'setTimeout', 'run', 'wait', 'stop', 'getCommandLine',
            'getExitCode', 'getExitCodeText', 'getWorkingDirectory',
            'isOutputDisabled', 'getErrorOutput'
        );
        $process->shouldReceive('isSuccessful')->andReturn(config('command-should-succeed'));
        $process->shouldReceive('getOutput')->andReturn('sent for execution');
        
        return $process;
    }

and my runCommand method calls for

        $process = $this->getProcess($cmd, $cwd);

then in my test I set a config parameter

        config(['command-should-succeed' => true]);

not sure if it is the best solution but works perfectly (:

jekinney
jekinney
1 year ago (213,695 XP)

Really can't in queued method. But for testing you can override your env setting and use queue sync (or what ever the default is). Set it as you would a testing database as in some of the videos here.

swalker
swalker
1 year ago (96,450 XP)

For this test I made a mock of Process inside the job

    private function getProccess($cmd,$cwd)
    {
        if (app()->environment() != 'testing'){
            return new Process(trim($cmd), $cwd);
        }
        
        $process = \Mockery::mock(Process::class);
        $process->shouldReceive(
            'setTimeout', 'run', 'wait', 'stop', 'getCommandLine',
            'getExitCode', 'getExitCodeText', 'getWorkingDirectory',
            'isOutputDisabled', 'getErrorOutput'
        );
        $process->shouldReceive('isSuccessful')->andReturn(config('command-should-succeed'));
        $process->shouldReceive('getOutput')->andReturn('sent for execution');
        
        return $process;
    }

and my runCommand method calls for

        $process = $this->getProcess($cmd, $cwd);

then in my test I set a config parameter

        config(['command-should-succeed' => true]);

not sure if it is the best solution but works perfectly (:

james2doyle

I ended up using the service container as an alias and then replace that in the test.

So I would have this in the AppServiceProvider:

// Alias App\Process to the Symfony Process so we can mock in tests
$this->app->bind('App\Process', function ($app, $args) {
     return new \Symfony\Component\Process\Process(...$args);
});

Then in my command I had a line like the following instead of doing the "getProcess" method:

$process = app('App\Process', [$cwd]);

Finally, I can update my test to be like so:

$process = Mockery::mock('Symfony\Component\Process\Process');
$process->shouldReceive(
    'setTimeout',
    'run',
    'wait',
    'stop',
    'getCommandLine',
    'getExitCode',
    'getExitCodeText',
    'getWorkingDirectory',
    'isOutputDisabled',
    'getErrorOutput'
);

$process->shouldReceive('isSuccessful')->andReturn(true);
$process->shouldReceive('getOutput')->andReturn('sent for execution');

// replace the instance of the Symfony Process with our mock
$app->bind('App\Process', function ($app, $args) use ($process) {
    return $process;
});

$this->expectOutputString('sent for execution');
$this->artisan('mycommand');

As you can see the test contains all the mock stuff now instead of the actual command.

Please sign in or create an account to participate in this conversation.