Skip to content

Commit ff9283e

Browse files
authored
Child process test double: (#430)
- ChildProcess::class now implements Contracts\ChildProcess::class interface - Facades\ChildProcess::fake() swaps implementations with a test double concrete - Implement new binding in NativeServiceProvider::class - Implement Contracts/ChildProcess::class methods, mimicing that of the implementation - Implement ChildProcessFake::class with assertion helpers - Test that ChildProcessFake::class assertions work
1 parent 3b3d8dc commit ff9283e

File tree

6 files changed

+518
-4
lines changed

6 files changed

+518
-4
lines changed

src/ChildProcess.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace Native\Laravel;
44

55
use Native\Laravel\Client\Client;
6+
use Native\Laravel\Contracts\ChildProcess as ChildProcessContract;
67

7-
class ChildProcess
8+
class ChildProcess implements ChildProcessContract
89
{
910
public readonly int $pid;
1011

src/Contracts/ChildProcess.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Native\Laravel\Contracts;
4+
5+
interface ChildProcess
6+
{
7+
public function get(?string $alias = null): ?self;
8+
9+
public function all(): array;
10+
11+
public function start(
12+
string|array $cmd,
13+
string $alias,
14+
?string $cwd = null,
15+
?array $env = null,
16+
bool $persistent = false
17+
): self;
18+
19+
public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self;
20+
21+
public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self;
22+
23+
public function stop(?string $alias = null): void;
24+
25+
public function restart(?string $alias = null): ?self;
26+
27+
public function message(string $message, ?string $alias = null): self;
28+
}

src/Facades/ChildProcess.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
namespace Native\Laravel\Facades;
44

55
use Illuminate\Support\Facades\Facade;
6-
use Native\Laravel\ChildProcess as Implement;
6+
use Native\Laravel\Contracts\ChildProcess as ChildProcessContract;
7+
use Native\Laravel\Fakes\ChildProcessFake;
78

89
/**
910
* @method static \Native\Laravel\ChildProcess[] all()
@@ -17,10 +18,17 @@
1718
*/
1819
class ChildProcess extends Facade
1920
{
21+
public static function fake()
22+
{
23+
return tap(static::getFacadeApplication()->make(ChildProcessFake::class), function ($fake) {
24+
static::swap($fake);
25+
});
26+
}
27+
2028
protected static function getFacadeAccessor()
2129
{
22-
self::clearResolvedInstance(Implement::class);
30+
self::clearResolvedInstance(ChildProcessContract::class);
2331

24-
return Implement::class;
32+
return ChildProcessContract::class;
2533
}
2634
}

src/Fakes/ChildProcessFake.php

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<?php
2+
3+
namespace Native\Laravel\Fakes;
4+
5+
use Closure;
6+
use Native\Laravel\Contracts\ChildProcess as ChildProcessContract;
7+
use PHPUnit\Framework\Assert as PHPUnit;
8+
9+
class ChildProcessFake implements ChildProcessContract
10+
{
11+
/**
12+
* @var array<int, string|null>
13+
*/
14+
public array $gets = [];
15+
16+
/**
17+
* @var array<int, array{cmd: array|string, alias: string, cwd: string|null, env: array|null, persistent: bool}>
18+
*/
19+
public array $starts = [];
20+
21+
/**
22+
* @var array<int, array{cmd: array|string, alias: string, env: array|null, persistent: bool}>
23+
*/
24+
public array $phps = [];
25+
26+
/**
27+
* @var array<int, array{cmd: array|string, alias: string, env: array|null, persistent: bool}>
28+
*/
29+
public array $artisans = [];
30+
31+
/**
32+
* @var array<int, string|null>
33+
*/
34+
public array $stops = [];
35+
36+
/**
37+
* @var array<int, string|null>
38+
*/
39+
public array $restarts = [];
40+
41+
/**
42+
* @var array<int, array{message: string, alias: string|null}>
43+
*/
44+
public array $messages = [];
45+
46+
public function get(?string $alias = null): self
47+
{
48+
$this->gets[] = $alias;
49+
50+
return $this;
51+
}
52+
53+
public function all(): array
54+
{
55+
return [$this];
56+
}
57+
58+
public function start(
59+
array|string $cmd,
60+
string $alias,
61+
?string $cwd = null,
62+
?array $env = null,
63+
bool $persistent = false
64+
): self {
65+
$this->starts[] = [
66+
'cmd' => $cmd,
67+
'alias' => $alias,
68+
'cwd' => $cwd,
69+
'env' => $env,
70+
'persistent' => $persistent,
71+
];
72+
73+
return $this;
74+
}
75+
76+
public function php(
77+
array|string $cmd,
78+
string $alias,
79+
?array $env = null,
80+
?bool $persistent = false
81+
): self {
82+
$this->phps[] = [
83+
'cmd' => $cmd,
84+
'alias' => $alias,
85+
'env' => $env,
86+
'persistent' => $persistent,
87+
];
88+
89+
return $this;
90+
}
91+
92+
public function artisan(
93+
array|string $cmd,
94+
string $alias,
95+
?array $env = null,
96+
?bool $persistent = false
97+
): self {
98+
$this->artisans[] = [
99+
'cmd' => $cmd,
100+
'alias' => $alias,
101+
'env' => $env,
102+
'persistent' => $persistent,
103+
];
104+
105+
return $this;
106+
}
107+
108+
public function stop(?string $alias = null): void
109+
{
110+
$this->stops[] = $alias;
111+
}
112+
113+
public function restart(?string $alias = null): self
114+
{
115+
$this->restarts[] = $alias;
116+
117+
return $this;
118+
}
119+
120+
public function message(string $message, ?string $alias = null): self
121+
{
122+
$this->messages[] = [
123+
'message' => $message,
124+
'alias' => $alias,
125+
];
126+
127+
return $this;
128+
}
129+
130+
/**
131+
* @param string|Closure(string): bool $alias
132+
*/
133+
public function assertGet(string|Closure $alias): void
134+
{
135+
if (is_callable($alias) === false) {
136+
PHPUnit::assertContains($alias, $this->gets);
137+
138+
return;
139+
}
140+
141+
$hit = empty(
142+
array_filter(
143+
$this->gets,
144+
fn (mixed $get) => $alias($get) === true
145+
)
146+
) === false;
147+
148+
PHPUnit::assertTrue($hit);
149+
}
150+
151+
/**
152+
* @param Closure(array|string $cmd, string $alias, ?string $cwd, ?array $env, bool $persistent): bool $callback
153+
*/
154+
public function assertStarted(Closure $callback): void
155+
{
156+
$hit = empty(
157+
array_filter(
158+
$this->starts,
159+
fn (array $started) => $callback(...$started) === true
160+
)
161+
) === false;
162+
163+
PHPUnit::assertTrue($hit);
164+
}
165+
166+
/**
167+
* @param Closure(array|string $cmd, string $alias, ?array $env, ?bool $persistent): bool $callback
168+
*/
169+
public function assertPhp(Closure $callback): void
170+
{
171+
$hit = empty(
172+
array_filter(
173+
$this->phps,
174+
fn (array $php) => $callback(...$php) === true
175+
)
176+
) === false;
177+
178+
PHPUnit::assertTrue($hit);
179+
}
180+
181+
/**
182+
* @param Closure(array|string $cmd, string $alias, ?array $env, ?bool $persistent): bool $callback
183+
*/
184+
public function assertArtisan(Closure $callback): void
185+
{
186+
$hit = empty(
187+
array_filter(
188+
$this->artisans,
189+
fn (array $artisan) => $callback(...$artisan) === true
190+
)
191+
) === false;
192+
193+
PHPUnit::assertTrue($hit);
194+
}
195+
196+
/**
197+
* @param string|Closure(string): bool $alias
198+
*/
199+
public function assertStop(string|Closure $alias): void
200+
{
201+
if (is_callable($alias) === false) {
202+
PHPUnit::assertContains($alias, $this->stops);
203+
204+
return;
205+
}
206+
207+
$hit = empty(
208+
array_filter(
209+
$this->stops,
210+
fn (mixed $stop) => $alias($stop) === true
211+
)
212+
) === false;
213+
214+
PHPUnit::assertTrue($hit);
215+
}
216+
217+
/**
218+
* @param string|Closure(string): bool $alias
219+
*/
220+
public function assertRestart(string|Closure $alias): void
221+
{
222+
if (is_callable($alias) === false) {
223+
PHPUnit::assertContains($alias, $this->restarts);
224+
225+
return;
226+
}
227+
228+
$hit = empty(
229+
array_filter(
230+
$this->restarts,
231+
fn (mixed $restart) => $alias($restart) === true
232+
)
233+
) === false;
234+
235+
PHPUnit::assertTrue($hit);
236+
}
237+
238+
/**
239+
* @param Closure(string $message, string|null $alias): bool $callback
240+
*/
241+
public function assertMessage(Closure $callback): void
242+
{
243+
$hit = empty(
244+
array_filter(
245+
$this->messages,
246+
fn (array $message) => $callback(...$message) === true
247+
)
248+
) === false;
249+
250+
PHPUnit::assertTrue($hit);
251+
}
252+
}

src/NativeServiceProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
use Illuminate\Support\Arr;
88
use Illuminate\Support\Facades\Artisan;
99
use Illuminate\Support\Facades\DB;
10+
use Native\Laravel\ChildProcess as ChildProcessImplementation;
1011
use Native\Laravel\Commands\FreshCommand;
1112
use Native\Laravel\Commands\LoadPHPConfigurationCommand;
1213
use Native\Laravel\Commands\LoadStartupConfigurationCommand;
1314
use Native\Laravel\Commands\MigrateCommand;
1415
use Native\Laravel\Commands\MinifyApplicationCommand;
1516
use Native\Laravel\Commands\SeedDatabaseCommand;
17+
use Native\Laravel\Contracts\ChildProcess as ChildProcessContract;
1618
use Native\Laravel\Contracts\WindowManager as WindowManagerContract;
1719
use Native\Laravel\Events\EventWatcher;
1820
use Native\Laravel\Exceptions\Handler;
@@ -54,6 +56,10 @@ public function packageRegistered()
5456
return $app->make(WindowManagerImplementation::class);
5557
});
5658

59+
$this->app->bind(ChildProcessContract::class, function (Foundation $app) {
60+
return $app->make(ChildProcessImplementation::class);
61+
});
62+
5763
if (config('nativephp-internal.running')) {
5864
$this->app->singleton(
5965
\Illuminate\Contracts\Debug\ExceptionHandler::class,

0 commit comments

Comments
 (0)