Testovanie
Jwt::fake() nahradí manager zaznamenávajúcim fakom nad párom RSA kľúčov v pamäti — súbory s kľúčmi netreba a prázdne jwt.issuer, jwt.audience či servisné tajomstvo doplní testovacími hodnotami. Denylist balíka presunie do súkromného cache store v pamäti, takže Redis (ani nič, čo uvádza jwt.denylist.store) netreba; vašu vlastnú väzbu Denylist nechá tak. Tokeny sa naďalej skutočne podpisujú aj overujú a každé vydanie, servisný token aj zakázanie sa zaznamená — cez fasádu, vložený JwtManager, guard(), services() aj denylist():
use App\Models\User;
use RoundlyConsulting\Jwt\Facades\Jwt;
use RoundlyConsulting\Jwt\Jose\Claims;
use RoundlyConsulting\Jwt\UserTokens\AccessTokenRequest;
it('mints, calls billing and logs out', function (): void {
$fake = Jwt::fake(); // in-memory RSA keys — no key files
$issued = Jwt::mintAccessToken(AccessTokenRequest::for('42'));
Jwt::services()->issue('billing', ['job' => 'sync']);
Jwt::logout($issued);
$fake->assertMinted(); // any user token
$fake->assertMinted(fn (Claims $claims): bool => $claims->string('sub') === '42');
$fake->assertServiceTokenIssued('billing');
$fake->assertServiceTokenIssued('billing', fn (Claims $claims): bool => $claims->get('job') === 'sync');
$fake->assertDenied($issued->jti);
$fake->assertDenied(); // any jti
expect($fake->minted())->toHaveCount(1); // list<RecordedToken> — ->token, ->claims
});
it('serves the profile to an authenticated user', function (): void {
$fake = Jwt::fake();
$user = User::factory()->create();
// Authenticate a jwt guard for the rest of the test — no hand-built token:
$fake->actingAs(['sub' => (string) $user->id, 'permissions' => ['posts.edit']], 'api');
$this->getJson('/me')->assertOk();
$fake->assertNothingMinted(); // actingAs() is not recorded as a mint
$fake->assertNothingIssuedToServices();
$fake->assertNothingDenied();
});Asercie
| Asercia | Negatívna | Prejde, keď |
|---|---|---|
assertMinted(?Closure $where = null) | assertNothingMinted() | Vydal sa používateľský token — pri callbacku taký, ktorého Claims mu vyhovujú — cez fasádu, vložený JwtManager alebo guard(). |
assertServiceTokenIssued(?string $audience = null, ?Closure $where = null) | assertNothingIssuedToServices() | Vydal sa servisný token — pre dané audience a vyhovujúci callbacku, ak ich zadáte — vrátane request() a authenticate(). |
assertDenied(?string $jti = null) | assertNothingDenied() | Do denylistu sa dostal nejaký jti (alebo práve tento) — cez denylist()->deny() / denyToken(), logout() či denyClaims(). |
$fake->minted(), serviceTokens() a denied() vrátia zaznamenané údaje — objekty RecordedToken s token a claims a zakázané jti.
Prihlásenie guardu
actingAs($claims, $guard) vydá skutočný token pre audience a scope guardu — nezaznamená sa ako vydanie — a pošle ho ako bearer každého ďalšieho requestu, ktorý nemá vlastnú hlavičku Authorization. Vráti IssuedToken, napríklad pre $this->withToken(). Pri Eloquent provideri musí byť sub kľúč skutočného používateľa; guard s token_version potrebuje zodpovedajúci tv.
actingAs() z Laravelu
Aj actingAs() z Laravelu funguje na oboch guardoch, úplne bez tokenu — guard si nastaveného používateľa podrží až do Auth::guard('api')->forgetUser():
$this->actingAs($user, 'api')->getJson('/me')->assertOk(); // Eloquent or TokenUser
$this->actingAs($serviceIdentity, 'service')->postJson('/internal/sync')->assertOk();Jwt::guard('api')->claims() vráti claimy nastaveného TokenUser (a Jwt::services()->claims() claimy nastavenej ServiceIdentity); iný takto nastavený používateľ claimy nemá. Jwt::fake() volajte pred actingAs() z Laravelu — fake guardy znova zostaví a skôr nastaveného používateľa zahodí — a neskoršie $fake->actingAs() ho nahradí.
Testy so skutočnými kľúčmi
Ak chcete otestovať vlastnú konfiguráciu kľúčov, testujte so skutočnými tokenmi. Raz vygenerujte dočasný pár kľúčov pre testy — relatívne cesty sa odvodzujú od koreňa aplikácie:
JWT_PRIVATE_KEY_PATH=tests/keys/jwt-private.pem \
JWT_PUBLIC_KEY_PATH=tests/keys/jwt-public.pem \
php artisan jwt:generate-keysPoužívateľské tokeny a guard jwt
Piny a kľúče nastavte v každom teste, denylist držte v pamäti cez cache store array a overujte cez HTTP vrstvu:
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use RoundlyConsulting\Jwt\Events\UserTokenIssued;
use RoundlyConsulting\Jwt\Facades\Jwt;
use RoundlyConsulting\Jwt\Jose\Exceptions\TokenExpired;
use RoundlyConsulting\Jwt\UserTokens\AccessTokenRequest;
beforeEach(function (): void {
config([
'jwt.issuer' => 'jwt-issuer',
'jwt.audience' => 'web',
'jwt.private_key_path' => base_path('tests/keys/jwt-private.pem'),
'jwt.public_key_path' => base_path('tests/keys/jwt-public.pem'),
'jwt.denylist.store' => 'array',
'auth.guards.api' => ['driver' => 'jwt'],
]);
Route::middleware('auth:api')->get('/me', fn () => ['id' => auth()->id()]);
});
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
it('authenticates a bearer token and rejects it after logout', function (): void {
$issued = Jwt::mintAccessToken(AccessTokenRequest::for('user-1'));
$this->withToken($issued->token)->getJson('/me')
->assertOk()
->assertJson(['id' => 'user-1']);
Jwt::logout($issued);
$this->withToken($issued->token)->getJson('/me')->assertUnauthorized();
});
it('rejects an expired token', function (): void {
$issued = Jwt::mintAccessToken(AccessTokenRequest::for('user-1')->ttl(60));
CarbonImmutable::setTestNow(CarbonImmutable::now()->addMinutes(5));
expect(fn () => Jwt::verify($issued->token))->toThrow(TokenExpired::class);
});
it('dispatches UserTokenIssued', function (): void {
Event::fake();
$issued = Jwt::mintAccessToken(AccessTokenRequest::for('user-1'));
Event::assertDispatched(UserTokenIssued::class, fn (UserTokenIssued $e): bool => $e->jti === $issued->jti);
});Časové kontroly čítajú CarbonImmutable::now(), takže CarbonImmutable::setTestNow() posunie token za expiráciu — po každom teste čas vráťte späť.
Servisné tokeny
Nastavte tajomstvo s aspoň 32 bajtmi, názov tejto služby a guard service-jwt, potom overujte prichádzajúce volania aj odchádzajúce hlavičky cez Http::fake():
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use RoundlyConsulting\Jwt\Facades\Jwt;
beforeEach(function (): void {
config([
'jwt.service.name' => 'notifications',
'jwt.service.secret' => 'a-very-secret-service-key-0123456789',
'jwt.service.issuer' => 'billing',
'auth.guards.service' => ['driver' => 'service-jwt'],
]);
});
it('accepts a signed internal call', function (): void {
Route::middleware('auth:service')->get('/internal', fn () => ['iss' => auth()->id()]);
$token = Jwt::services()->issue('notifications')->token;
$this->withToken($token)->getJson('/internal')
->assertOk()
->assertJson(['iss' => 'billing']);
});
it('signs outbound internal calls', function (): void {
Http::fake();
Jwt::services()->request('notifications')->post('https://notifications.internal/send', ['id' => 1]);
Http::assertSent(fn ($request) => $request->hasHeader('Authorization'));
});Vlastná sada testov balíka sa spúšťa cez composer test:
composer testPrejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.