ConvertKit - v1.0.0
Testing
The package ships FakeConvertKit — a recording test double that swaps the container binding so consumer app tests can drive Kit calls without touching the network.
Install the fake
use ArtisanPackUI\ConvertKit\Facades\ConvertKit;
$fake = ConvertKit::fake();
ConvertKit::fake() binds a fresh FakeConvertKit against both:
- The
'convertkit'container alias (so the facade +convertkit()helper return the fake). - The concrete
ArtisanPackUI\ConvertKit\ConvertKit::classbinding (so constructor-injected consumers — including theProcessKitFeedandSubscribeToKitjobs — resolve the fake too).
Call it once at the top of the test (or in beforeEach). It returns the fake so you can drive assertions.
Assertions
assertSubscribed( string $email, ?int $formId = null ): void
Assert that an email was subscribed. Passing $formId also requires that the subscribe went through that specific Kit form (via forms()->subscribe()); leave it null to match any subscribe path.
$fake->assertSubscribed( 'jane@example.com' );
$fake->assertSubscribed( 'jane@example.com', 12345 );
assertTagged( string $email, int $tagId ): void
Assert that a tag was applied to a subscribed email. Matches either a standalone subscribers()->tag() call or a forms()->subscribe() that carried the tag in its payload.
$fake->assertTagged( 'jane@example.com', 100 );
assertNothingSent(): void
Assert that no subscribe, tag, or untag calls were recorded.
$fake->assertNothingSent();
assertSentCount( int $count ): void
Assert an exact number of subscribe calls.
$fake->assertSentCount( 1 );
Counts only distinct subscribe calls — tag applies do not increment.
Public recording arrays
For custom assertions the built-in helpers don't cover:
$fake->subscribed— every subscribe call:[ [ 'email', 'first_name', 'fields', 'form_id', 'tags' ], … ].$fake->tagged— every standalonetag()call:[ [ 'email', 'tag_id' ], … ].$fake->untagged— everyuntag()call:[ [ 'email', 'tag_id' ], … ].
expect( $fake->subscribed )->toHaveCount( 3 );
expect( $fake->subscribed[0]['fields'] )->toBe( [ 'company' => 'Acme' ] );
Return values
Every fake endpoint method returns a plausible-looking Subscriber DTO so downstream code that uses the return value doesn't have to be different in tests:
subscribers()->create()andforms()->subscribe()return aSubscriberwith a fresh incrementing id (1, 2, 3, …),state = 'active', and the fields you passed in.subscribers()->find(id)returns aSubscriberwith that id and empty email.subscribers()->findByEmail(email)returns a matching subscriber if you recorded one viacreate; otherwisenull.subscribers()->unsubscribe(id)returns aSubscriberwithstate = 'cancelled'.- List endpoints (
list(),refresh()) return[].
Example — a controller test
use ArtisanPackUI\ConvertKit\Facades\ConvertKit;
it( 'subscribes users on signup', function (): void {
$fake = ConvertKit::fake();
$this->post( '/signup', [
'email' => 'jane@example.com',
'name' => 'Jane',
] )->assertRedirect( '/dashboard' );
$fake->assertSubscribed( 'jane@example.com' );
$fake->assertTagged( 'jane@example.com', 100 );
$fake->assertSentCount( 1 );
} );
Example — a queued job under the fake
Since ConvertKit::fake() also binds the concrete class, any queued job that type-hints ConvertKit in its handle() will run against the fake even if you're processing the queue synchronously.
use ArtisanPackUI\ConvertKit\Facades\ConvertKit;
use ArtisanPackUI\ConvertKit\Jobs\SubscribeToKit;
it( 'the queued job hits Kit', function (): void {
$fake = ConvertKit::fake();
( new SubscribeToKit(
email: 'a@b.co',
firstName: null,
fields: [],
tagIds: [ 1, 2 ],
kitFormId: 555,
) )->handle( $fake );
$fake->assertSubscribed( 'a@b.co', 555 );
$fake->assertTagged( 'a@b.co', 1 );
$fake->assertTagged( 'a@b.co', 2 );
} );
Testing the forms integration end-to-end
Flip the integration on inside the test, fake ConvertKit, then dispatch the forms event manually.
use ArtisanPackUI\ConvertKit\Facades\ConvertKit;
use ArtisanPackUI\ConvertKit\Models\KitFeed;
it( 'fires a feed when the form is submitted', function (): void {
config()->set( 'convertkit.forms_integration.enabled', true );
$feed = KitFeed::factory()->create( [
'kit_form_id' => 555,
'kit_tag_ids' => [ 100 ],
'field_map' => [ 'email_address' => 'email' ],
] );
$fake = ConvertKit::fake();
// Dispatch a stand-in for the forms event.
event( new \Tests\Stubs\FormSubmittedStub(
submission: (object) [ 'id' => 1, 'form_id' => $feed->form_id, 'data_array' => [ 'email' => 'a@b.co' ] ],
) );
// With sync queue, the job ran inline; assert against the fake.
$fake->assertSubscribed( 'a@b.co', 555 );
$fake->assertTagged( 'a@b.co', 100 );
} );
Set QUEUE_CONNECTION=sync in the test env so dispatch() runs the job in-process.
Faking events instead
If you don't care about the Kit call itself — only that an event fired — use Laravel's Event::fake():
use Illuminate\Support\Facades\Event;
use ArtisanPackUI\ConvertKit\Events\KitSubscribed;
Event::fake( [ KitSubscribed::class ] );
// … drive the code …
Event::assertDispatched( KitSubscribed::class );
You can combine both — the fake fires events under the hood, and Event::fake will observe them.