jcergolj / fake-it
Laravel Fakes for your ordinary application and service classes.
Requires
- php: >=8.3
- illuminate/container: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0 || ^11.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0 || ^12.0
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
instanceofand autowiring just work. -
Assert afterwards —
assertCalled(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)
-
finalclasses throwFakeIt\CannotFake. Dropfinal, or usedg/bypass-finals. -
Static methods are not faked — they run the real implementation.
-
Direct
new X()is not intercepted; only container-resolved classes are replaced. -
readonlyand 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.