Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Function Mock Laravel Package

avris/function-mock

Mock PHP functions in unit tests without changing your code. avris/function-mock lets you replace and control built-in and user-defined function calls at runtime, making it easier to test legacy code, edge cases, and hard-to-isolate behavior.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Install the Package:

    composer require avris/function-mock --dev
    

    No Laravel-specific configuration is needed; the package works as a standalone utility.

  2. Basic Usage in Tests: Initialize the mock in your test class's setUp() method (PHPUnit) or beforeEach() (Pest):

    use Avris\FunctionMock\FunctionMock;
    
    public function setUp(): void
    {
        $this->mock = new FunctionMock();
        $this->mock->mock('str_replace', function ($search, $replace, $subject) {
            return "mocked-$subject";
        });
    }
    
    public function tearDown(): void
    {
        $this->mock->unmock('str_replace');
    }
    
  3. First Test Case: Test a function that relies on str_replace without hitting the real implementation:

    public function test_string_replacement()
    {
        $result = str_replace('foo', 'bar', 'hello foo world');
        $this->assertEquals('mocked-hello foo world', $result);
    }
    
  4. Where to Look First:

    • Package Source: src/FunctionMock.php (single class, ~100 lines).
    • API Docs: Focus on:
      • mock(string $functionName, callable $callback): Replace a function.
      • unmock(string $functionName): Restore original function.
      • isMocked(string $functionName): Debugging helper.

Implementation Patterns

Core Workflows

1. Mocking for Deterministic Tests

Replace unreliable or external-dependent functions (e.g., file_get_contents, time):

$mock = new FunctionMock();
$mock->mock('file_get_contents', fn($url) => '{"status": "mocked"}');
$mock->mock('time', fn() => 1625097600); // Freeze time to 2021-06-30

2. Namespace Scoping

Limit mocks to specific namespaces to avoid collisions:

$mock->mockInNamespace('App\\Services', 'log_error', fn($message) => null);
// Only affects calls from `App\Services\*`.

3. Callback-Based Mocks

Use closures for dynamic return values or side effects:

$mock->mock('rand', function ($min, $max) {
    static $counter = 0;
    return $counter++ % 2 === 0 ? 10 : 20; // Alternate between 10 and 20
});

4. Integration with Laravel Testing

Combine with Laravel’s testing tools (e.g., HTTP tests):

public function test_api_with_mocked_time()
{
    $mock = new FunctionMock();
    $mock->mock('time', fn() => 1700000000); // 2023-11-15

    $response = $this->get('/api/time-sensitive');
    $mock->unmock('time'); // Cleanup

    $response->assertJson(['date' => '2023-11-15']);
}

5. Mocking Custom Global Functions

For user-defined globals (e.g., config() in Laravel), wrap them in a class first or use runtime overrides:

// Avoid: Mocking `config()` directly (not supported).
// Instead: Refactor or use Laravel’s service container mocking.

Laravel-Specific Patterns

1. Mocking Laravel Helpers

Target Laravel’s global helpers (e.g., config(), app()) by mocking their underlying functions:

$mock->mock('app', fn($make = null) => $this->app); // Redirect to Laravel container

2. Service Provider Testing

Test providers that rely on global functions (e.g., file_exists):

public function test_provider_registration()
{
    $mock = new FunctionMock();
    $mock->mock('file_exists', fn($path) => true);

    $this->app->register(ServiceProvider::class);
    // Test logic...
    $mock->unmock('file_exists');
}

3. Artisan Command Mocking

Mock functions called by Artisan commands (e.g., date for timestamped logs):

public function test_command_with_fixed_time()
{
    $mock = new FunctionMock();
    $mock->mock('date', fn($format) => '2023-01-01');

    $this->artisan('command:log')
         ->expectsOutput('2023-01-01');
    $mock->unmock('date');
}

4. Event Listener Testing

Mock functions in event listeners (e.g., json_encode for payloads):

public function test_event_listener()
{
    $mock = new FunctionMock();
    $mock->mock('json_encode', fn($data) => '{"mocked": true}');

    $this->dispatch(new Event());
    // Assert mock was called...
    $mock->unmock('json_encode');
}

5. Middleware Mocking

Simulate external API calls in middleware:

public function test_middleware_with_mocked_api()
{
    $mock = new FunctionMock();
    $mock->mock('file_get_contents', fn($url) => '{"status": "ok"}');

    $response = $this->get('/api/protected');
    $response->assertJson(['status' => 'ok']);
    $mock->unmock('file_get_contents');
}

Advanced Patterns

1. Tracking Mock Calls

Use closures to log or assert function invocations:

$calls = [];
$mock->mock('log_error', function ($message) use (&$calls) {
    $calls[] = $message;
    return true;
});

// Later...
$this->assertCount(1, $calls);

2. Conditional Mocks

Return different values based on input:

$mock->mock('str_contains', function ($haystack, $needle) {
    return $needle === 'error' ? false : true;
});

3. Exception Mocking

Simulate function failures:

$mock->mock('json_decode', function ($json) {
    throw new \RuntimeException('Invalid JSON');
});

4. Temporary Mocks in Closures

Useful for one-off tests or callbacks:

$mock = new FunctionMock();
$mock->mock('time', fn() => 1625097600);

$result = tap(function () use ($mock) {
    return strftime('%Y', time()); // Returns "2021"
}, fn() => $mock->unmock('time'));

5. Mocking in Factories

Override functions used by model factories:

public function test_factory_with_mocked_uuid()
{
    $mock = new FunctionMock();
    $mock->mock('ramsey\uuid\Uuid::uuid4', fn() => 'mocked-uuid');

    $user = User::factory()->create();
    $this->assertEquals('mocked-uuid', $user->uuid);
    $mock->unmock('ramsey\uuid\Uuid::uuid4');
}

Gotchas and Tips

Pitfalls

  1. Namespace Collisions:

    • Mocking str_replace in a global scope may interfere with custom str_replace functions in namespaced classes.
    • Fix: Use mockInNamespace() to scope mocks:
      $mock->mockInNamespace('App\\Helpers', 'str_replace', ...);
      
  2. Forgotten unmock():

    • Mocks persist until explicitly removed, causing test pollution.
    • Fix: Always call unmock() in tearDown() or use a context manager:
      $mock = new FunctionMock();
      $mock->mock('time', fn() => 1625097600);
      try {
          // Test logic...
      } finally {
          $mock->unmock('time');
      }
      
  3. Static Method Confusion:

    • The package only mocks global functions, not static methods (e.g., User::generateToken()).
    • Fix: Use Mockery or partialMock for static methods.
  4. Performance Overhead:

    • Mocks add minimal runtime overhead but can slow down tests if overused.
    • Fix: Profile tests with Xdebug to identify bottlenecks.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package