jcergolj/fake-it

Laravel Fakes for your ordinary application and service classes.

Maintainers

Package info

github.com/jcergolj/fake-it

pkg:composer/jcergolj/fake-it

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

v3 2026-08-21 11:42 UTC

This package is auto-updated.

Last update: 2026-08-25 16:26:45 UTC


README

Laravel Fakes for your ordinary application classes.

Swap a real class in the container with a generated, call-recording fake and assert on what happened — real type hints, no shouldReceive(), no Mockery::close().

Requires PHP 8.3+ and Laravel 12+.

Why

Application services are usually tested by running the real implementation (slow, can't assert "was this called?") or by reaching for Mockery. FakeIt brings Laravel's Queue::fake() / Event::fake() ergonomics to your classes:

  • Real type hints — the fake is a subclass of your class, so instanceof and autowiring just work.

  • Assert afterwardsassertCalled(fn ($user) => ...), not expectations declared up front.

  • No test leakage — the fake resets automatically when Laravel rebuilds the container between tests.

  • No mocking DSL — a tiny returns()-style helper instead of a Mockery-powered expectation language.

Install

If you want the Laravel-style YourClass::fake() API via the Fakeable trait, install the package as a normal dependency:

composer require jcergolj/fake-it

Add one line to any class you control:

use FakeIt\Concerns\Fakeable;

class UserRegistration
{
    use Fakeable;
}

If you prefer to keep the package as a test-only dependency, do not add the trait to classes in app/. Instead, install it with --dev and use the runtime-free API from your tests:

composer require --dev jcergolj/fake-it
use FakeIt\FakeIt;

FakeIt::of(UserRegistration::class);

Usage

The examples below use the Fakeable trait for the Laravel-style YourClass::fake() API. That path requires jcergolj/fake-it in require, not require-dev.

public function test_it_registers_a_user(): void
{
    UserRegistration::fake();

    // The framework injects the fake wherever UserRegistration is needed.
    $this->post('/register', ['email' => 'a@b.c']);

    UserRegistration::assertCalled(
        'register',
        fn (User $user) => $user->id === $expectedId
    );
}

Faking classes you don't control

This API also works for classes you do control when you want to keep FakeIt in require-dev and avoid referencing the package from app/ classes.

use FakeIt\FakeIt;

FakeIt::of(Vendor\PaymentGateway::class)
    ->returns('charge', $receipt)
    ->assertCalled('charge');

Passing constructor arguments

By default the fake is built without running the real constructor. If a faked method relies on constructor-seeded state (a collaborator the real constructor stores), pass the constructor arguments and the real constructor runs via reflection — named arguments, optional params, variadics, and protected / private constructors are all supported:

use FakeIt\FakeIt;

FakeIt::of(CodeGiveawayValidator::class, ['request' => $request])
    ->returns('fails', false)
    ->assertCalled('validate');

To re-instantiate a class that is already faked (e.g. after SomeService::fake()), use the fluent with() — it swaps in a constructor-seeded instance and re-binds any as() abstracts:

CodeGiveawayValidator::fake()->with(['request' => $request]);

Omitting a required argument throws FakeIt\CannotInstantiate.

Partial fakes

By default every public method is replaced: unstubbed methods return a default value (or throw FakeIt\MissingReturnStub for object returns). Sometimes you want the real implementation for most methods and only stub a few. Chain partial() to make unstubbed methods call parent::method(...) — stubs and all call assertions still work as usual:

use FakeIt\FakeIt;

FakeIt::of(PaymentGateway::class, ['apiKey' => $key])
    ->partial()
    ->returns('charge', $receipt);   // only `charge` is stubbed; the rest run for real

Because partial methods run the real code, the real constructor must run — pass its arguments via of() or with().

To stub a method and let a different one run for real:

FakeIt::of(Calculator::class, ['scale' => 10])
    ->partial()
    ->returns('audit', null);        // `audit` is stubbed; `add`/`subtract` run for real

Before & after

Before — Mockery:

public function test_it_registers_a_user()
{
    $mailer = Mockery::mock(Mailer::class);
    $mailer->shouldReceive('send')->once();
    $this->app->instance(Mailer::class, $mailer);

    $service = new UserRegistration($mailer);   // you wire it by hand
    $service->register($user);

    Mockery::close();
}

protected function tearDown(): void
{
    Mockery::close();   // forget this and tests leak state
    parent::tearDown();
}

After — FakeIt:

public function test_it_registers_a_user()
{
    UserRegistration::fake();

    // The framework injects the fake wherever UserRegistration is needed.
    $this->post('/register', ['email' => 'a@b.c']);

    UserRegistration::assertCalled(
        'register',
        fn (User $user) => $user->id === $expectedId
    );
}

// No tearDown. No Mockery::close(). Fakes reset automatically per test.

API

Trait-based statics:

UserRegistration::fake();                              // swap the container binding
UserRegistration::fake(['apiKey' => $key]);             // …and run the real constructor with args
UserRegistration::restore();                           // revert this class only (mid-test)

UserRegistration::returns('register', $registration)   // stub a return (chainable)
    ->returns('cancel', null);
UserRegistration::throws('register', new \Exception('boom')); // the "return" is an exception

UserRegistration::assertCalled('register');            // called at least once
UserRegistration::assertCalled('register',             // …satisfied by a callback
    fn (User $user) => $user->id === 123);
UserRegistration::assertCalledTimes('register', 2);    // exactly N times
UserRegistration::assertNotCalled('cancel');           // never called
UserRegistration::assertNothingCalled();              // no method called at all

// Single-method fakes need no method name — it is inferred (throws if ambiguous):
ProcessTokenAccessAction::assertCalled(                // infers `handle`
    fn ($token, $channel) => $token instanceof Token,
);

// Assertions return the handle, so they chain:
FakeIt::of(CodeGiveawayValidator::class)
    ->assertCalled('validate')
    ->assertCalled('fails')
    ->assertCalled('getErrors');

// A single-method fake needs no method name — it is inferred, just like
// Laravel's Queue::assertPushed(Class::class, ...):
ProcessTokenAccessAction::assertCalled(
    fn ($token, $channel) => $token instanceof Token,
);

FakeIt::of(PaymentGateway::class, ['apiKey' => $key])   // pass constructor arguments
    ->partial()                                          // unstubbed methods run for real
    ->with(['apiKey' => $otherKey]);                     // re-seed the constructor on the active fake

$calls = UserRegistration::called('register');         // Collection of Call records
//   ->args   the original arguments (array)
//   ->return the returned value (or the thrown exception)

Dev-only-friendly alternative:

use FakeIt\FakeIt;

FakeIt::of(UserRegistration::class)->returns('register', $registration);
FakeIt::of(UserRegistration::class)->assertCalled('register');
FakeIt::restore(UserRegistration::class);

Unstubbed return values

Declared return type Fake returns
void nothing
nullable / mixed / union containing null null
int / float / string / bool / array 0 / 0.0 / '' / false / []
non-nullable object / static / self / sealed union throws FakeIt\MissingReturnStub — stub it first

Limitations (v1)

  • final classes throw FakeIt\CannotFake. Drop final, or use dg/bypass-finals.

  • Static methods are not faked — they run the real implementation.

  • Direct new X() is not intercepted; only container-resolved classes are replaced.

  • readonly and interface-implementing concrete classes are fully supported.

  • Faking purely by interface (UserRegistrar::fake()) is deferred to a later version.

Run it

php examples/feature-test.php       # fake a collaborator, run the real service, then assert
php examples/stubbing.php           # stub a return value and inspect the raw call log
php examples/reset-and-nothing.php  # a fresh fake records nothing; stub an exception
php examples/with-constructor.php   # pass constructor arguments so the real constructor runs
php examples/partial.php             # stub a few methods; let the rest run for real
php examples/chaining.php             # assertions return the handle, so they chain

Or run the full suite (Orchestra Testbench):

vendor/bin/phpunit

License

MIT.