---
title: "Testing — Certificates for Laravel | Roundly"
description: "Certificates::fake() records every issue, renewal, revoke, expire, sync and prune in memory, with an assertion for each — plus the array driver."
url: https://roundly-consulting.com/open-source/docs/certificates-for-laravel/testing
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Certificates for Laravel](https://roundly-consulting.com/open-source/docs/certificates-for-laravel.md)

# Testing

Certificates::fake() swaps an in-memory Testing\\CertificatesFake in for the facade and the container, so constructor-injected CertificatesManagers, the for() handle and HasCertificates::requestCertificate() are all recorded. It returns the fake, and every assertion is callable on it or on the facade:

```php
use RoundlyConsulting\Certificates\Facades\Certificates;

$fake = Certificates::fake();

$this->post('/sites', ['domain' => 'app.example.com']);

Certificates::assertIssued('app.example.com');
Certificates::assertNothingRevoked();
```

- It never touches a provider, the registry, the queue or the event bus — certificates live in memory and every driver() is an in-memory ArrayProvider.
- Invalid domains (InvalidDomainException), unknown domains, illegal status moves — renewLater() included — and a certificate name another domain already holds still throw, exactly as they do for real.
- issue() without a driver records the configured default driver and keeps the meta, as a real issue does. status(), exists() and the builder’s reads honour a driver; issueIfMissing() and status() also find a SAN certificate covering the host.
- renewDue() works on the in-memory store — failed rows, stuck renewing ones and certificates already past expiry included — and returns a real RenewalReport; sync() and prune() return 0.

## Assertions

| Recorded by | Assertions |
| --- | --- |
| issue, issueIfMissing, generate, for()->issue(), requestCertificate() | `assertIssued($domain), assertNotIssued($domain), assertIssuedCount($n), assertNothingIssued(), assertRequested($domain)` |
| recordFailure($domain) | `assertFailed($domain)` |
| renew, for()->renew() (and inline renewDue) | `assertRenewed($domain), assertNotRenewed($domain), assertNothingRenewed()` |
| renewLater, for()->renewLater() (and renewDue(queue: true)) | `assertRenewedLater($domain), assertNothingRenewedLater()` |
| renewDue | `assertRenewedDue(?$thresholdDays), assertNothingRenewedDue()` |
| a renewal failed via failRenewalOf() | `assertRenewalFailed($domain), assertNoRenewalFailures()` |
| revoke, for()->revoke() | `assertRevoked($domain, ?$reason), assertNotRevoked($domain), assertNothingRevoked()` |
| expire, for()->expire() | `assertExpired($domain), assertNothingExpired()` |
| sync | `assertSynced(?$driver), assertNothingSynced()` |
| prune | `assertPruned(?$days), assertNothingPruned()` |

Every assertion in use:

```php
use RoundlyConsulting\Certificates\Facades\Certificates;

$fake = Certificates::fake();

// Issuance — issue, issueIfMissing, generate, for()->issue(), requestCertificate()
Certificates::issueIfMissing('a.example.com');
Certificates::for('b.example.com')->issue();
$fake->recordFailure('c.example.com');

Certificates::assertIssued('a.example.com');
Certificates::assertNotIssued('c.example.com');
Certificates::assertIssuedCount(2);
Certificates::assertRequested('c.example.com');   // issued, or recorded as failed
Certificates::assertFailed('c.example.com');

// Renewals — renew, renewLater, renewDue
Certificates::renew('a.example.com');
Certificates::for('b.example.com')->renewLater();
Certificates::renewDue(7);

Certificates::assertRenewed('a.example.com');
Certificates::assertNotRenewed('b.example.com');
Certificates::assertRenewedLater('b.example.com');
Certificates::assertRenewedDue(7);                // a run with exactly this threshold
Certificates::assertNoRenewalFailures();

// Revoke and expire
Certificates::revoke('a.example.com', 'key compromise');
Certificates::for('b.example.com')->expire();

Certificates::assertRevoked('a.example.com', 'key compromise');
Certificates::assertNotRevoked('b.example.com');
Certificates::assertExpired('b.example.com');

// Registry maintenance
Certificates::sync('kubernetes');
Certificates::prune(30);

Certificates::assertSynced('kubernetes');
Certificates::assertPruned(30);
```

And the “nothing happened” counterparts:

```php
Certificates::fake();

// ...code that must leave certificates alone...

Certificates::assertNothingIssued();
Certificates::assertNothingRenewed();
Certificates::assertNothingRenewedLater();
Certificates::assertNothingRenewedDue();
Certificates::assertNothingRevoked();
Certificates::assertNothingExpired();
Certificates::assertNothingSynced();
Certificates::assertNothingPruned();
```

## Seeding and failing renewals

| Method | Purpose |
| --- | --- |
| `seed(Certificate ...$certificates)` | Store certificates in memory without recording an issuance, to renew, revoke or expire something the test did not issue. Returns the fake. |
| `failRenewalOf(string ...$domains)` | Make those renewals fail: the row turns Failed, renew() throws, renewDue() reports it under failed and carries on. Returns the fake. |
| `recordFailure(string $domain)` | Record a failed issuance — drives assertFailed() and assertRequested(). |

seed() and failRenewalOf() let you test how your code handles a RenewalReport with failures:

```php
use RoundlyConsulting\Certificates\Facades\Certificates;
use RoundlyConsulting\Certificates\Models\Certificate;

$fake = Certificates::fake()
    ->seed(Certificate::factory()->forDomain('a.example.com')->expiring(5)->make())
    ->failRenewalOf('a.example.com');

$report = Certificates::renewDue();

expect($report->hasFailures())->toBeTrue()
    ->and($report->failedDomains())->toBe(['a.example.com']);

Certificates::assertRenewalFailed('a.example.com');
```

## Expiry monitors

monitorExpiry() builds an alerts schedule, so fake it with the alerts Health::fake() and assertMonitored():

```php
use RoundlyConsulting\Alerts\Facades\Health;
use RoundlyConsulting\Certificates\Alerts\CertificateExpiryCheck;
use RoundlyConsulting\Certificates\Facades\Certificates;

$health = Health::fake();

Certificates::monitorExpiry($certificate, $opsTeam)->daily()->save();

$health->assertMonitored(CertificateExpiryCheck::class, $opsTeam);
```

## Against the array driver

To exercise the real issue path — registry rows, events, owners — skip the fake and switch the default driver to the in-memory array driver. Run with the migrations loaded (RefreshDatabase):

```php
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Certificates\Events\CertificateIssued;
use RoundlyConsulting\Certificates\Facades\Certificates;

it('records an issued certificate', function () {
    config(['certificates.default' => 'array']);
    Event::fake([CertificateIssued::class]);

    $certificate = Certificates::issueIfMissing('app.example.com');

    expect($certificate->status->isActive())->toBeTrue()
        ->and($certificate->expires_at)->not->toBeNull();

    Event::assertDispatched(CertificateIssued::class);
});
```

## Model factory

The registry model ships a factory with states for the common scenarios:

```php
use RoundlyConsulting\Certificates\Models\Certificate;

Certificate::factory()->create();                // pending, random domain, kubernetes driver
Certificate::factory()->issued()->create();      // issued, expires in 90 days
Certificate::factory()->expiring(5)->create();   // issued, expires in 5 days
Certificate::factory()->expired()->create();     // expired 5 days ago
Certificate::factory()->failed()->create();      // failed, with last_error
Certificate::factory()->forDomain('shop.example.com')->issued()->create();
```

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

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

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/certificates-for-laravel.md)
