NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Likes::fake() swaps the manager — for the facade and for injected LikeManagers — with a recording fake and returns it. The fake still performs every operation, so has(), for() and your own queries see real rows. A write the package refuses — an unknown reaction type, no resolvable actor — throws and is not recorded, so an assertion never passes over a write that didn’t happen:

use RoundlyConsulting\Likes\Facades\Likes;

$fake = Likes::fake();

$user->like($post);
Likes::actor($user)->unlike($comment);
Likes::actor($user)->as('love')->react($photo);

$fake->assertLiked($post);                    // optionally a type: an untyped like matches the default type
$fake->assertLikedBy($user, $post);
$fake->assertNotLiked($other);
$fake->assertLikedCount(1);
$fake->assertLikedTimes($post, 1);
$fake->assertUnliked($comment, by: $user);
$fake->assertReacted($photo, 'love');

Fake assertions

AssertionPasses when
assertLiked($model, ?$type)A like was recorded for the model (optionally of that type).
assertLikedBy($actor, $model, ?$type)That actor liked the model (optionally with that type).
assertNotLiked($model)No like was recorded for the model.
assertNothingLiked()No likes were recorded at all.
assertLikedCount($count)Exactly that many likes were recorded.
assertLikedTimes($model, $count)The model was liked exactly that many times.
assertUnliked($model, ?$type, ?$by)An unlike was recorded for the model (optionally of that type, by that actor).
assertNothingUnliked()No unlikes were recorded at all.
assertReacted($model, ?$type, ?$by)A react() was recorded for the model (optionally of that type, by that actor).
assertNothingReacted()No reactions were recorded at all.

The negative assertions prove a request wrote nothing:

$fake = Likes::fake();

$this->get('/posts');                         // a read-only page

$fake->assertNothingLiked();
$fake->assertNothingUnliked();
$fake->assertNothingReacted();

What the fake records

  • Every completed write, whichever way it was made — the facade, the actor()/as() builder, an injected LikeManager, the GivesLikes trait and the InteractsWithLikes helpers.
  • Bulk likeMany() / unlikeMany() record one entry per model.
  • A toggle is recorded as the like or unlike it performed; react() is recorded as a reaction — assert it with assertReacted(), not assertLiked().
  • A type filter matches the reaction written: an untyped like matches default_reaction.
  • Calls are recorded as made, so an idempotent repeat still counts towards assertLikedCount() and assertLikedTimes().

Acting-actor helpers

The InteractsWithLikes trait adds acting-actor helpers. They go through the manager, so a Likes::fake() in the same test records them too:

uses(RoundlyConsulting\Likes\Testing\InteractsWithLikes::class);

$this->actingAsLiker($user);
$this->likeAs($post, 'love');
$this->unlikeAs($post);
$this->toggleAs($post);
$this->likeAs($post, null, $otherUser);   // a one-off actor

likeAs(), unlikeAs() and toggleAs() take (model, ?type, ?actor). Without an actor argument or a prior actingAsLiker() call they throw a RuntimeException.

Pest matchers

Register the matchers once in tests/Pest.php:

// tests/Pest.php
RoundlyConsulting\Likes\Testing\LikeExpectations::register();

// in a test
expect($post)->toBeLikedBy($user);
expect($post)->toBeLikedBy($user, 'love');
expect($post)->toHaveReaction('love');

The matchers are guarded by function_exists('expect'), so Pest is never pulled into your runtime.

Factory

Seed likes directly with the model factory:

use RoundlyConsulting\Likes\Models\Like;

Like::factory()
    ->forActor($user)
    ->forLikeable($post)
    ->ofType('love')
    ->create();

The factory writes rows directly — no events, no allowlist check — which makes it ideal for arranging state before the behaviour you test.

A full example

use RoundlyConsulting\Likes\Facades\Likes;

it('lets a reader love a post', function () {
    config()->set('likes.reactions', ['like', 'love']);

    $fake = Likes::fake();
    $user = User::factory()->create();
    $post = Post::factory()->create();

    Likes::actor($user)->as('love')->like($post);

    $fake->assertLikedBy($user, $post, 'love');
    expect($post)->toBeLikedBy($user, 'love')      // LikeExpectations::register() in Pest.php
        ->and($post->reactionSummary()->top)->toBe('love');
});

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 crypto

By 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.