Requests::fake() swaps a recording fake in behind the facade and the container and returns it, so the facade, the fluent builder and any constructor-injected RequestManager all land on it. Mutating calls are recorded and never touch the database — no hand-rolled Event::fake() needed; canTransition() still answers for real:
use RoundlyConsulting\Requests\Facades\Requests;
it('submits an expense claim', function () {
$fake = Requests::fake();
$alice = User::factory()->create();
$request = Requests::make()->title('Expense')->create();
Requests::approve($request, $alice, 'Within budget');
$fake->assertCreated(1);
$fake->assertApproved($request, $alice, 'Within budget'); // or Requests::assertApproved(...)
$fake->assertNothingRejected();
});
it('submits nothing when the form is invalid', function () {
$fake = Requests::fake();
// …
$fake->assertNothingCreated();
});Every assertion
| Assertion | Passes when |
|---|---|
assertCreated(?int $times = null) | At least one request was created — or exactly $times. |
assertNothingCreated() | No request was created. |
assertApproved(Request, ?Model $actor, ?string $reason) | approve() was called for the request — by that actor and with that reason, when given. |
assertNothingApproved() | approve() was never called. |
assertRejected(Request, ?Model $actor, ?string $reason) | The same, for reject(). |
assertNothingRejected() | reject() was never called. |
assertReopened(Request, ?Model $actor, ?string $reason) | The same, for reopen(). |
assertNothingReopened() | reopen() was never called. |
assertCancelled(Request) | cancel() was called for the request. |
assertNothingCancelled() | cancel() was never called. |
assertExpired(Request) | expire() was called for the request. |
assertNothingExpired() | expire() was never called. |
assertExpiredDue(?bool $dryRun = null) | expireDue() ran — with that dry-run flag, when given. |
assertNothingExpiredDue() | expireDue() never ran. |
$fake = Requests::fake();
$fake->assertCreated(); // at least one — or assertCreated(2) for exactly two
$fake->assertApproved($request); // actor and reason are optional filters
$fake->assertRejected($request, $carol, 'Out of policy');
$fake->assertReopened($request, $alice);
$fake->assertCancelled($request);
$fake->assertExpired($request);
$fake->assertExpiredDue(); // expireDue() ran
$fake->assertExpiredDue(dryRun: true); // …as a dry run
// Every assertion has a negative twin
$fake->assertNothingCreated();
$fake->assertNothingApproved();
$fake->assertNothingRejected();
$fake->assertNothingReopened();
$fake->assertNothingCancelled();
$fake->assertNothingExpired();
$fake->assertNothingExpiredDue();The asserts are also callable statically on the facade. The fake’s create() returns an unsaved Request filled with the status, type, title, description and expiry, and approve(), reject() and reopen() record the reason you pass. expireDue() returns how many requests are due without expiring any — so requests:expire run under the fake is recorded too.
Factory states
The Request model ships a factory with a state per status plus helpers for approvers and authors. requiring() stores approver ids only — it opens no approval request:
use RoundlyConsulting\Requests\Models\Request;
Request::factory()->pending()->create();
Request::factory()->approved()->create();
Request::factory()->rejected()->create();
Request::factory()->cancelled()->create();
Request::factory()->expired()->create(); // New, expires_at yesterday
Request::factory()->requiring([$alice->id, $bob->id])->create();
Request::factory()->authoredBy($user)->create();Against the real engine
For integration tests, load both the requests and the approvals migrations and drive the real flow:
use RoundlyConsulting\Requests\Enums\Status;
use RoundlyConsulting\Requests\Facades\Requests;
use RoundlyConsulting\Requests\Models\Request;
it('approves once both managers sign off', function () {
$alice = User::factory()->create();
$bob = User::factory()->create();
$request = Requests::make()->requireApprovalsFrom([$alice, $bob])->create();
Requests::approve($request, $alice);
expect($request->fresh()->status)->toBe(Status::New);
Requests::approve($request, $bob);
expect($request->fresh()->status)->toBe(Status::Approved);
});
it('expires stale requests', function () {
Request::factory()->expired()->create();
$this->artisan('requests:expire')
->expectsOutput('Expired 1 request(s).')
->assertSuccessful();
});The package’s own suite runs with:
composer testShow your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.