Skip to main content
When a package calls external commands such as ffmpeg or git, differences in the host application’s environment and command failures become the package’s behavior. Instead of calling shell_exec() or exec() directly, run commands through the Process facade and replace them with Process::fake() in tests.
This page targets Laravel 13 and checks the implementation of Framework v13.42.0. It assumes processes and package testing. The array command matching covered here relies on how Symfony Process, which Process uses internally, converts arguments.

Why to avoid direct execution

Direct function calls cannot be swapped out in tests, and each caller handles timeouts and exit codes differently. Going through the Process facade gives you one consistent way to record runs, fake them, apply timeouts and throw exceptions on failure.

Pass commands as arrays

A string command is interpreted by sh, so including user input opens the door to quoting problems and injection. Inside a package, pass arguments as an array.
The default timeout of PendingProcess is 60 seconds, and the idle timeout is null. Set timeout() explicitly for long conversions, and add idleTimeout() for work whose output can stall.

Exceptions and messages on failure

ProcessResult::throw() throws ProcessFailedException when the exit code is not successful. The message contains the command that ran, the exit code, and the standard output and standard error output.
If you put secrets in command arguments, they end up in the ProcessFailedException message and can appear in logs or on screen. Do not pass tokens as arguments. Pass them with env() or write them to the command’s configuration file instead.
Timeouts are represented by ProcessTimedOutException, and idle timeouts by ProcessIdleTimedOutException. Document for your package’s users which exceptions you throw and what they can expect when they catch them.

Fake in tests

In tests, replace external commands with Process::fake(). Process::result() lets you specify the standard output, the standard error output and the exit code.
When you pass a closure to assertRan(), you can check the command, timeout, environment and other properties of PendingProcess. Array commands are compared with $process->command by exact match, so tests detect changes to the order or values of arguments.

Fake keys for array commands

Internally, Process matches the keys passed to fake() against the result of getCommandline() with Str::is(). Array commands are converted into a quoted string for the shell, so a string pattern such as 'ffmpeg*' does not match. For tests of array commands, use one of the following.

Prevent unexpected external commands

To detect external commands that should not run in tests, use preventStrayProcesses(). Commands that do not match a fake throw a RuntimeException and never actually start.

When you use an injected Factory

If your package’s constructor receives Illuminate\Process\Factory, check whether the Process::fake() state reaches the injected instance. The facade caches the resolved instance, and swap() replaces the instance in the container. Whether Factory is shared in the container, however, is not something you can rely on the framework to register explicitly, so verify it in a test. If the fake does not reach the injected instance, swap the Factory explicitly in the test or design the package to go through the Process facade. In either case, confirming with assertRan() that the call was recorded lets you catch a missed swap.

Checks to add for long-term maintenance

Primary sources

Last modified on November 13, 2026