Comments::fake() swaps in a recording CommentsFake — behind the facade and the container, so injected CommentsManagers get it too. It still performs every operation — rows are written, policies and locks apply, events fire — and records each successful mutation, however it was made: the facade, an injected manager, the builder, a query’s bulk moderation, $user->writeComment() or $comment->lockReplies():
use RoundlyConsulting\Comments\Facades\Comments;
use RoundlyConsulting\Comments\Models\Comment;
$fake = Comments::fake();
// ... run the code under test ...
$fake->assertPosted(fn (Comment $comment) => $comment->comment === 'Hi');
$fake->assertApproved($comment);
$fake->assertLocked($post);
$fake->assertThreadLocked();
$fake->assertNothingDeleted();Every assertion
Each assertion takes an optional model (matched with is()) or a callback that returns true on a match; with no argument, any recorded call passes. They are also callable statically once faked, e.g. Comments::assertPosted():
| Recorded by | Assert | Assert none |
|---|---|---|
on()->post(), write(), writeComment() | assertPosted() | assertNothingPosted() |
update() | assertUpdated() | assertNothingUpdated() |
delete(), deleteAll() | assertDeleted() | assertNothingDeleted() |
restore() | assertRestored() | assertNothingRestored() |
approve(), approveAll() | assertApproved() | assertNothingApproved() |
hide(), hideAll() | assertHidden() | assertNothingHidden() |
lock($subject) | assertLocked() | assertNothingLocked() |
unlock($subject) | assertUnlocked() | assertNothingUnlocked() |
lockThread(), lockReplies() | assertThreadLocked() | assertNothingThreadLocked() |
unlockThread(), unlockReplies() | assertThreadUnlocked() | assertNothingThreadUnlocked() |
$fake = Comments::fake();
$user->writeComment($post, 'Hi'); // recorded by the trait path too
Comments::update($comment, 'Edited');
Comments::for($post)->pending()->approveAll(); // one record per approved row
Comments::hide($spam);
Comments::delete($old);
Comments::restore($old);
Comments::lock($post);
Comments::unlock($post);
$comment->lockReplies();
$comment->unlockReplies();
$fake->assertPosted(fn (Comment $c) => $c->comment === 'Hi');
$fake->assertUpdated($comment);
$fake->assertApproved(); // any approval at all
$fake->assertHidden($spam);
$fake->assertDeleted($old);
$fake->assertRestored($old);
$fake->assertLocked($post);
$fake->assertUnlocked(fn ($subject) => $subject->is($post));
$fake->assertThreadLocked($comment);
$fake->assertThreadUnlocked($comment);
// In a test where nothing should change:
$fake->assertNothingPosted();
$fake->assertNothingUpdated();
$fake->assertNothingDeleted();
$fake->assertNothingRestored();
$fake->assertNothingApproved();
$fake->assertNothingHidden();
$fake->assertNothingLocked();
$fake->assertNothingUnlocked();
$fake->assertNothingThreadLocked();
$fake->assertNothingThreadUnlocked();A call that throws records nothing. The auto-hide listener moderates on the system’s behalf without a user, so it bypasses the manager and is not recorded — assert CommentHidden with Event::fake() instead.
Factory states
The Comment factory ships states for every shape of comment — on($subject), pending(), hidden(), locked(), reply($parent) and by($author). A comment needs a subject, so every factory comment takes on($subject) or reply($parent), or Eloquent’s for($subject, 'commentable'):
use RoundlyConsulting\Comments\Models\Comment;
$root = Comment::factory()->on($post)->create();
Comment::factory()->on($post)->pending()->create();
Comment::factory()->on($post)->hidden()->create();
Comment::factory()->on($post)->locked()->create();
Comment::factory()->reply($root)->by($user)->create(); // inherits the root's subject
Comment::factory()->for($post, 'commentable')->create(); // the plain Eloquent form works tooDatabase assertions
Use the AssertsComments trait in a test case for readable expectations:
namespace Tests\Feature;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use RoundlyConsulting\Comments\Facades\Comments;
use RoundlyConsulting\Comments\Testing\AssertsComments;
use Tests\TestCase;
final class PostCommentsTest extends TestCase
{
use AssertsComments;
use RefreshDatabase;
public function test_a_user_can_comment_on_a_post(): void
{
$post = Post::factory()->create();
$user = User::factory()->create();
Comments::on($post)->as($user)->body('expected body')->post();
$this->assertCommented($post);
$this->assertCommented($post, 'expected body');
$this->assertCommentCount($post, 1);
}
public function test_a_new_post_has_no_comments(): void
{
$this->assertNotCommented(Post::factory()->create());
}
}The assertions count every non-deleted comment on the subject, whatever its status or depth.
Faking events
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Comments\Events\CommentApproved;
use RoundlyConsulting\Comments\Events\CommentCreated;
use RoundlyConsulting\Comments\Facades\Comments;
Event::fake([CommentCreated::class, CommentApproved::class]);
$comment = Comments::on($post)->as($user)->body('Hello')->post();
Comments::approve($comment);
Event::assertDispatched(CommentCreated::class, fn ($e) => $e->comment->is($comment));
Event::assertDispatched(CommentApproved::class);Run the suite with the package’s migrations published — and those of Likes, Reports, Approvals and Media Library — for example with RefreshDatabase.
Show 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.