Testovanie
Kubernetes::fake() vymení manager — za fasádou aj v kontajneri, takže ho dostanú aj injektované KubernetesManagery — za apiserver v pamäti a vráti objekt KubernetesFake. Nič neodíde do siete. Každý klaster z managera (pomenovaný, predvolený, ad-hoc, obmedzený na namespace) aj každý naň naviazaný zdroj komunikuje s fake, ktorý každú požiadavku zaznamená:
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;
use RoundlyConsulting\KubernetesApi\Resources\Deployment;
it('scales checkout for the sale', function () {
$fake = Kubernetes::fake()->seed(Deployment::class, [
['metadata' => ['name' => 'checkout', 'namespace' => 'shop'], 'spec' => ['replicas' => 2]],
]);
app(PrepareForSale::class)(); // your code: Kubernetes::namespace('shop')->deployments()->setName('checkout')->scale(10)
$fake->assertScaled(Deployment::class, 'checkout', 10);
$fake->assertNothingDeleted();
});Fake sa správa ako malý apiserver: naplnené aj vytvorené objekty môžete vypisovať (selektory podľa labelov a polí, limit a continue, všetky namespace), hľadať, upravovať, patchovať, škálovať a mazať. Chýbajúci objekt vráti skutočnú 404, duplicitné vytvorenie či zastaraná resourceVersion skutočnú 409 a požiadavky s dryRun() sa overia bez uloženia. Každé meno klastra má vlastné úložisko; ad-hoc klastre zdieľajú úložisko predvoleného klastra.
Príprava
| Príprava | Účinok |
|---|---|
seed(string $resource, array $items, ?string $cluster = null) | Vloží objekty — manifesty alebo objekty zdrojov — na klaster. $resource je trieda alebo zaregistrované meno ('pods'). |
seedLogs(string $pod, string $logs, string $namespace = 'default', ?string $cluster = null) | Čo vrátia logs() a streamLogs(); tailLines sa rešpektuje. |
stubExec(ExecResult|Closure $result) | Čo vráti exec() — pevný výsledok alebo closure, ktorá dostane RecordedRequest. Predvolene prázdny výstup a návratový kód 0. |
stubVersion(VersionInfo|string $version) | Čo nahlási version(); predvolene v1.34.0. |
unreachable(bool $unreachable = true) | Každá požiadavka vyhodí ConnectionException; ping() vráti false. |
recorded(?Closure $filter = null) | Každý RecordedRequest, ktorý fake dostal, voliteľne filtrovaný. |
Logy, exec aj sledovanie zmien fungujú aj proti fake — watch pošle jednu udalosť ADDED za každý vyhovujúci objekt:
use RoundlyConsulting\KubernetesApi\DataTransferObjects\ExecResult;
use RoundlyConsulting\KubernetesApi\DataTransferObjects\PodLogOptions;
use RoundlyConsulting\KubernetesApi\DataTransferObjects\WatchEvent;
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;
it('serves seeded pods, logs and exec', function (): void {
$fake = Kubernetes::fake()
->seed('pods', [
['metadata' => ['name' => 'api', 'namespace' => 'shop', 'labels' => ['app' => 'web']]],
])
->seedLogs('api', "one\ntwo\nthree\n", namespace: 'shop')
->stubExec(new ExecResult("hi\n", '', 0));
$pod = Kubernetes::namespace('shop')->pods()->setName('api');
expect($pod->logs(new PodLogOptions(tailLines: 2)))->toBe("two\nthree")
->and(iterator_to_array($pod->streamLogs(), false))->toBe(['one', 'two', 'three'])
->and($pod->exec(['sh', '-c', 'echo hi'])->stdout)->toBe("hi\n");
// A watch streams one ADDED event per matching object
$events = [];
Kubernetes::namespace('shop')->pods()->whereLabel('app', 'web')
->watch(function (WatchEvent $event) use (&$events): void {
$events[] = $event;
});
expect($events)->toHaveCount(1)
->and($events[0]->isAdded())->toBeTrue();
$fake->assertExecuted('api', ['sh', '-c', 'echo hi']);
});Overenia
| Overenie · variant „nič“ | Príklad | Prejde, keď |
|---|---|---|
assertSent(Closure) · assertNothingSent() | $fake->assertSent(fn (RecordedRequest $r): bool => $r->cluster === 'production') | Niektorá požiadavka vyhovuje — alebo sa neodoslala žiadna. |
assertCreated($resource, $constraint = null) · assertNothingCreated() | $fake->assertCreated('configMaps', 'settings') | Zdroj sa vytvoril — voliteľne s týmto menom alebo podľa closure. |
assertUpdated($resource, $constraint = null) · assertNothingUpdated() | $fake->assertUpdated(Deployment::class, 'checkout') | Celá úprava — update() alebo updateOrCreate() nad existujúcim objektom. |
assertPatched($resource, $constraint = null) · assertNothingPatched() | $fake->assertPatched('deployments', 'checkout') | Akýkoľvek PATCH — vrátane patch(), scale() a rolloutRestart(). |
assertScaled($resource, $name, ?int $replicas = null) · assertNothingScaled() | $fake->assertScaled(Deployment::class, 'checkout', 10) | Volanie scale() — ak je zadaný počet replík, presne naň. |
assertRestarted($resource, $name) · assertNothingRestarted() | $fake->assertRestarted(Deployment::class, 'checkout') | Volanie rolloutRestart(). |
assertDeleted($resource, $constraint = null) · assertNothingDeleted() | $fake->assertDeleted('pods', 'api') | Zmazanie. |
assertExecuted(string $pod, ?array $command = null) · assertNothingExecuted() | $fake->assertExecuted('api', ['sh', '-c', 'echo hi']) | exec() v danom pode — ak je zadaný príkaz, presne tento. |
$resource je trieda zdroja alebo zaregistrované meno ('pods'); obmedzením je meno objektu alebo closure, ktorá dostane RecordedRequest. Požiadavky v režime dry-run sa zaznamenajú, no overenia zmien nikdy nesplnia:
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;
use RoundlyConsulting\KubernetesApi\Resources\Deployment;
use RoundlyConsulting\KubernetesApi\Testing\RecordedRequest;
$fake->assertCreated('configMaps', fn (RecordedRequest $request): bool => $request->namespace === 'shop'
&& $request->input('data.FEATURE_X') === 'on');
// Everything the fake received, optionally filtered
$dryRuns = $fake->recorded(fn (RecordedRequest $request): bool => $request->isDryRun());
// The assertions are also callable on the facade once it is faked
Kubernetes::assertScaled(Deployment::class, 'checkout', 10);RecordedRequest nesie cluster (pri ad-hoc klastroch null), method, path, verb, apiVersion, plural, namespace, name, subresource, query, dekódované body, contentType a pri exec command a container — a k tomu isDryRun(), input('spec.replicas') a isRolloutRestart().
Čo fake nerobí
- Klastre zaregistrované pred fake() zostávajú a ich definície sa naďalej spúšťajú; pod fake však fromKubeConfig(), inCluster() ani zdroje kubeconfig a in-cluster v konfigurácii nikdy nečítajú prihlasovacie údaje.
- Rovnako ako apiserver odpovie fake chybou 422 na server-side apply bez fieldManager alebo na force pri inom type patchu.
- Patche strategic-merge a server-side apply sa aplikujú ako JSON merge patche a logy aj exec odpovedajú pre akýkoľvek pod.
- Klastre vytvorené mimo managera — new Cluster, Cluster::make() — fake obídu.
Kontrola na úrovni HTTP cez Http::fake()
Http::fake() naďalej funguje — zachytí skutočný HTTP transport, keď chcete overiť presné URL a hlavičky. V takých testoch vypnite obmedzenie požiadaviek na strane klienta, aby veľká sada testov nikdy nečakala na rozpočet v pamäti:
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\KubernetesApi\Cluster;
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;
beforeEach(function (): void {
config(['kubernetes.rate_limits.enabled' => false]);
Kubernetes::registerCluster('testing', fn (Cluster $cluster): Cluster => $cluster
->url('https://k8s.example.com')
->withToken('fake-token')
->withManagerName('shop-tests'));
Http::preventStrayRequests();
});
it('lists the deployments in a namespace', function (): void {
Http::fake([
'*' => Http::response([
'items' => [
['metadata' => ['name' => 'checkout', 'namespace' => 'shop']],
],
]),
]);
$deployments = Kubernetes::cluster('testing')->deployments()->setNamespace('shop')->get();
expect($deployments)->toHaveCount(1)
->and($deployments->first()->getName())->toBe('checkout');
Http::assertSent(fn (Request $request): bool => $request->method() === 'GET'
&& $request->url() === 'https://k8s.example.com/apis/apps/v1/namespaces/shop/deployments?pretty=1'
&& $request->hasHeader('Authorization', 'Bearer fake-token'));
});exec() otvára vlastný WebSocket namiesto HTTP klienta, takže ho Http::fake() nezachytí — nasimulujte ho cez Kubernetes::fake() alebo overte presnú cestu subresource a dotaz, ktoré pre príkaz zostaví getExecPath():
$path = Kubernetes::cluster('testing')->pods()
->setNamespace('shop')
->withName('checkout-5f7c9d8b6-x2kqj')
->getExecPath(['php', 'artisan', 'cache:clear'], container: 'app');
expect($path)
->toContain('/api/v1/namespaces/shop/pods/checkout-5f7c9d8b6-x2kqj/exec?')
->toContain('container=app')
->toContain('command=cache%3Aclear');Testy samotného balíka
Predvolená sada testov balíka je úplne simulovaná a klaster nepotrebuje. Samostatná, voliteľná sada spúšťa skutočné CRUD, exec a výpisy proti lokálnemu klastru OrbStack; je chránená tak, že sa spustí len pri K8S_INTEGRATION=1, kontexte presne orbstack a hoste apiservera na loopbacku alebo *.orb.local, a každý beh používa dočasný namespace, ktorý sa potom zmaže:
composer test
# Opt-in live suite against a local OrbStack cluster
K8S_INTEGRATION=1 composer test-integration| Premenná | Predvolené | Účel |
|---|---|---|
K8S_INTEGRATION | nenastavená | Hodnota 1 (alebo true) sadu zapne; bez nej sa každý integračný test preskočí. |
K8S_INTEGRATION_CONTEXT | orbstack | Kontext kubeconfigu, z ktorého sada berie prihlasovacie údaje. Musí to byť orbstack. |
K8S_INTEGRATION_NAMESPACE | náhodný k8s-it-… | Prepíše dočasný namespace, ktorý sada vytvorí a po sebe zmaže. |
Prejavte 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.