TestFormRequests   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Test Coverage

Coverage 99.07%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 102
c 1
b 0
f 0
dl 0
loc 364
ccs 106
cts 107
cp 0.9907
rs 9.92
wmc 31

15 Methods

Rating   Name   Duplication   Size   Complexity  
A assertValidationPassed() 0 12 3
A makeRequestRedirector() 0 9 1
A assertValidationErrorsMissing() 0 14 3
A assertValidationFailed() 0 12 3
A validateFormRequest() 0 8 3
A assertValidationErrors() 0 14 3
A mockFormRequest() 0 6 1
A formRequest() 0 6 1
A succeed() 0 3 1
A assertAuthorized() 0 5 1
A assertNotAuthorized() 0 5 1
A setFormRequestFactory() 0 23 1
A assertValidationMessages() 0 16 3
A createFormRequestMock() 0 19 4
A createFormRequest() 0 31 2
1
<?php
2
3
namespace VGirol\FormRequestTester;
4
5
use \Mockery;
6
use Closure;
7
use Exception;
8
use Illuminate\Auth\Access\AuthorizationException;
9
use Illuminate\Foundation\Http\FormRequest;
10
use Illuminate\Routing\Redirector;
11
use Illuminate\Support\Arr;
12
use Illuminate\Support\Facades\Route;
13
use Illuminate\Validation\ValidationException;
14
use PHPUnit\Framework\Assert;
15
use PHPUnit\Framework\MockObject\MockObject;
16
17
/**
18
 * This trait provides some tools to test FormRequest (as concrete or abstract class).
19
 */
20
trait TestFormRequests
21
{
22
    abstract public static function assertTrue($condition, string $message = ''): void;
23
    abstract public static function assertFalse($condition, string $message = ''): void;
24
    abstract public static function assertContains($needle, iterable $haystack, string $message = ''): void;
25
26
    /**
27
     * The current form request that needs to be tested
28
     *
29
     * @var FormRequest|MockObject
30
     */
31
    private $currentFormRequest;
32
33
    /**
34
     * Validation errors
35
     * null when validation haven't been done yet
36
     *
37
     * @var array
38
     */
39
    private $errors = null;
40
41
    /**
42
     * The form requests authorization status
43
     *
44
     * @var boolean
45
     */
46
    private $formRequestAuthorized = true;
47
48
    /**
49
     * Create new instance of form request with
50
     *
51
     * @param string $formRequestType
52
     * @param array  $data            default to empty
53
     * @param array  $options         ['route' => 'Route to instantiate form request with',
54
     *                                'method' => 'to instantiate form request with']
55
     *
56
     * @return \Illuminate\Foundation\Http\FormRequest
57
     */
58 75
    public function createFormRequest(string $formRequestType, $data = [], $options = [])
59
    {
60 75
        $options = \array_merge(
61
            [
62 75
                'method' => 'POST',
63
                'route' => '/fake-route'
64
            ],
65 75
            $options
66
        );
67
68 75
        $currentFormRequest = $formRequestType::create($options['route'], $options['method'], $data)
69 75
            ->setContainer($this->app)
70 75
            ->setRedirector($this->makeRequestRedirector());
71
72 75
        $currentFormRequest->setRouteResolver(
73
            /**
74
             * @return Route
75
             */
76 75
            function () use ($currentFormRequest) {
77 12
                $routes = Route::getRoutes();
78
                try {
79 12
                    $route = $routes->match($currentFormRequest);
80 12
                } catch (\Exception $e) {
81 12
                    $route = null;
82
                } finally {
83 12
                    return $route;
84
                }
85 75
            }
86
        );
87
88 75
        return $currentFormRequest;
89
    }
90
91
    /**
92
     * Create a mock instance of form request.
93
     *
94
     * @param string             $formRequestType
95
     * @param array              $data            default to empty
96
     * @param array              $options         ['route' => 'Route to instantiate form request with',
97
     *                                            'method' => 'to instantiate form request with']
98
     * @param Closure|array|null $factory
99
     * The factory used to create the FormRequest mock depends of the value of the $factory parameter :
100
     * - if null, the "getMockForAbstractClass" method of PHPUnit library is used with default parameters
101
     * - if $factory is an array, the "getMockForAbstractClass" method of PHPUnit library is used with the parameters
102
     *   provided in the $factory array. This array must contain the six last parameters of the
103
     *   "getMockForAbstractClass" method (i.e. $mockClassName, $callOriginalConstructor, $callOriginalClone,
104
     *   $callAutoload, $mockedMethods, $cloneArguments).
105
     * - if $factory is a Closure, this Closure will be used to create the FormRequest mock. This Closure must be of
106
     *   the form :
107
     *     function (string $formRequestType, array $args): MockObject
108
     *
109
     * @return MockObject
110
     */
111 69
    public function createFormRequestMock(string $formRequestType, $data = [], $options = [], $factory = null)
112
    {
113 69
        if (\is_array($factory) || ($factory === null)) {
114 6
            $mockOptions = $factory ?? [];
115 6
            $factory = function (string $formRequestType, array $args) use ($mockOptions): MockObject {
116
                return \call_user_func_array(
117 6
                    [$this, 'getMockForAbstractClass'],
118 6
                    \array_merge([$formRequestType, $args], $mockOptions)
119
                );
120 6
            };
121
        }
122
123 69
        if (!($factory instanceof Closure)) {
124 3
            throw new Exception('$factory parameter must be of type Closure, array or null.');
125
        }
126
127 66
        $this->setFormRequestFactory($formRequestType, $factory);
128
129 66
        return $this->createFormRequest($formRequestType, $data, $options);
130
    }
131
132
    /**
133
     * Create and validate form request.
134
     *
135
     * @param string $formRequestType
136
     * @param array  $data            default to empty
137
     * @param array  $options         ['route' => 'Route to instantiate form request with',
138
     *                                'method' => 'to instantiate form request with']
139
     *
140
     * @return static
141
     */
142 3
    public function formRequest(string $formRequestType, $data = [], $options = [])
143
    {
144 3
        $this->currentFormRequest = $this->createFormRequest($formRequestType, $data, $options);
145 3
        $this->validateFormRequest();
146
147 3
        return $this;
148
    }
149
150
    /**
151
     * Create and validate mock for form request.
152
     *
153
     * @param string             $formRequestType
154
     * @param array              $data            default to empty
155
     * @param array              $options         ['route' => 'Route to instantiate form request with',
156
     *                                            'method' => 'to instantiate form request with']
157
     * @param Closure|array|null $factory
158
     *
159
     * @return static
160
     */
161 57
    public function mockFormRequest(string $formRequestType, $data = [], $options = [], $factory = null)
162
    {
163 57
        $this->currentFormRequest = $this->createFormRequestMock($formRequestType, $data, $options, $factory);
164 57
        $this->validateFormRequest();
165
166 57
        return $this;
167
    }
168
169
    /**
170
     * Undocumented function
171
     *
172
     * @param string $formRequestType
173
     *
174
     * @return void
175
     */
176 69
    public function setFormRequestFactory(string $formRequestType, Closure $factory)
177
    {
178 69
        $formRequestType::setFactory(
179 69
            function (
180
                array $query = [],
181
                array $request = [],
182
                array $attributes = [],
183
                array $cookies = [],
184
                array $files = [],
185
                array $server = [],
186
                $content = null
187
            ) use (
188 69
                $formRequestType,
189 69
                $factory
190
            ) {
191 69
                $formRequestType::setFactory(null);
192
193
                return \call_user_func_array(
194 69
                    $factory,
195
                    [
196 69
                        $formRequestType,
197
                        [
198 69
                            $query, $request, $attributes, $cookies, $files, $server, $content
199
                        ]
200
                    ]
201
                );
202 69
            }
203
        );
204 69
    }
205
206
    /**
207
     * create fake request redirector to be used in request
208
     *
209
     * @return \Illuminate\Routing\Redirector
210
     */
211 75
    private function makeRequestRedirector()
212
    {
213 75
        $fakeUrlGenerator = Mockery::mock();
214 75
        $fakeUrlGenerator->shouldReceive('to', 'route', 'action', 'previous')->withAnyArgs()->andReturn(null);
215
216 75
        $redirector = Mockery::mock(Redirector::class);
217 75
        $redirector->shouldReceive('getUrlGenerator')->andReturn($fakeUrlGenerator);
218
219 75
        return $redirector;
220
    }
221
222
    /**
223
     * validates form request and save the errors
224
     *
225
     * @return void
226
     */
227 60
    private function validateFormRequest()
228
    {
229
        try {
230 60
            $this->currentFormRequest->validateResolved();
231 42
        } catch (ValidationException $e) {
232 21
            $this->errors = $e->errors();
233 21
        } catch (AuthorizationException $e) {
234 21
            $this->formRequestAuthorized = false;
235
        }
236 60
    }
237
238
    /*----------------------------------------------------
239
     * Assertions functions
240
    --------------------------------------------------- */
241
    /**
242
     * assert form request validation have passed
243
     *
244
     * @return $this
245
     */
246 12
    public function assertValidationPassed()
247
    {
248 12
        if (!$this->formRequestAuthorized) {
249 3
            Assert::fail(Messages::NOT_AUTHORIZED);
250
        }
251
252 9
        if (!empty($this->errors)) {
253 3
            Assert::fail(Messages::FAILED);
254
        }
255
256 6
        $this->succeed(Messages::SUCCEED);
257 6
        return $this;
258
    }
259
260
    /**
261
     * Asserts form request validation have failed.
262
     *
263
     * @return $this
264
     */
265 9
    public function assertValidationFailed()
266
    {
267 9
        if (!$this->formRequestAuthorized) {
268 3
            Assert::fail(Messages::NOT_AUTHORIZED);
269
        }
270
271 6
        if (empty($this->errors)) {
272 3
            Assert::fail(Messages::SUCCEED);
273
        }
274
275 3
        $this->succeed(Messages::FAILED);
276 3
        return $this;
277
    }
278
279
    /**
280
     * Asserts the validation errors has the expected keys.
281
     *
282
     * @param array $keys
283
     *
284
     * @return $this
285
     */
286 9
    public function assertValidationErrors($keys)
287
    {
288 9
        if (!$this->formRequestAuthorized) {
289 3
            Assert::fail(Messages::NOT_AUTHORIZED);
290
        }
291
292 6
        foreach (Arr::wrap($keys) as $key) {
293 6
            $this->assertTrue(
294 6
                isset($this->errors[$key]),
295 6
                \sprintf(Messages::MISSING_ERROR, $key)
296
            );
297
        }
298
299 3
        return $this;
300
    }
301
302
303
    /**
304
     * Asserts the validation errors doesn't have the given keys.
305
     *
306
     * @param array $keys
307
     *
308
     * @return $this
309
     */
310 9
    public function assertValidationErrorsMissing($keys)
311
    {
312 9
        if (!$this->formRequestAuthorized) {
313 3
            Assert::fail(Messages::NOT_AUTHORIZED);
314
        }
315
316 6
        foreach (Arr::wrap($keys) as $key) {
317 6
            $this->assertTrue(
318 6
                !isset($this->errors[$key]),
319 6
                \sprintf(Messages::ERROR_NOT_MISSING, $key)
320
            );
321
        }
322
323 3
        return $this;
324
    }
325
326
    /**
327
     * Assert that validation has the expected messages.
328
     *
329
     * @param array $messages
330
     *
331
     * @return $this
332
     */
333 9
    public function assertValidationMessages($messages)
334
    {
335 9
        if (!$this->formRequestAuthorized) {
336 3
            Assert::fail(Messages::NOT_AUTHORIZED);
337
        }
338
339 6
        $errors = Arr::flatten(Arr::wrap($this->errors));
340 6
        foreach ($messages as $message) {
341 6
            $this->assertContains(
342 6
                $message,
343
                $errors,
344 6
                \sprintf(Messages::MISSING_MESSAGE, $message)
345
            );
346
        }
347
348 3
        return $this;
349
    }
350
351
    /**
352
     * assert that the current user was authorized by the form request
353
     *
354
     * @return $this
355
     */
356 6
    public function assertAuthorized()
357
    {
358 6
        $this->assertTrue($this->formRequestAuthorized, Messages::NOT_AUTHORIZED);
359
360 3
        return $this;
361
    }
362
363
    /**
364
     * assert that the current user was not authorized by the form request
365
     *
366
     * @return $this
367
     */
368 6
    public function assertNotAuthorized()
369
    {
370 6
        $this->assertFalse($this->formRequestAuthorized, Messages::AUTHORIZED);
371
372 3
        return $this;
373
    }
374
375
    /**
376
     * assert the success of the current test
377
     *
378
     * @param string $message
379
     * @return void
380
     */
381 9
    private function succeed($message = '')
382
    {
383 9
        $this->assertTrue(true, $message);
384 9
    }
385
}
386