Passed
Push — master ( 928c25...8579cb )
by Gerrit
02:16
created

tests/unit/Services/ArgumentCompilerTest.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Copyright (C) 2017  Gerrit Addiks.
4
 * This package (including this file) was released under the terms of the GPL-3.0.
5
 * You should have received a copy of the GNU General Public License along with this program.
6
 * If not, see <http://www.gnu.org/licenses/> or send me a mail so i can send you a copy.
7
 * @license GPL-3.0
8
 * @author Gerrit Addiks <[email protected]>
9
 */
10
11
namespace Addiks\SymfonyGenerics\Tests\Unit\Services;
12
13
use PHPUnit\Framework\TestCase;
14
use Addiks\SymfonyGenerics\Services\ArgumentCompiler;
15
use Addiks\SymfonyGenerics\Arguments\ArgumentContextInterface;
16
use Symfony\Component\HttpFoundation\RequestStack;
17
use Addiks\SymfonyGenerics\Arguments\ArgumentFactory\ArgumentFactory;
18
use Symfony\Component\HttpFoundation\Request;
19
use Addiks\SymfonyGenerics\Arguments\Argument;
20
use ReflectionFunctionAbstract;
21
use ReflectionParameter;
22
use ReflectionType;
23
use ReflectionException;
24
use Closure;
25
use Addiks\SymfonyGenerics\Controllers\ControllerHelperInterface;
26
27
final class ArgumentCompilerTest extends TestCase
28
{
29
30
    /**
31
     * @var ArgumentCompiler
32
     */
33
    private $compiler;
34
35
    /**
36
     * @var ArgumentFactory
37
     */
38
    private $argumentFactory;
39
40
    /**
41
     * @var RequestStack
42
     */
43
    private $requestStack;
44
45
    /**
46
     * @var ArgumentContextInterface
47
     */
48
    private $argumentContext;
49
50
    /**
51
     * @var ControllerHelperInterface
52
     */
53
    private $controllerHelper;
54
55
    public function setUp()
56
    {
57
        $this->argumentFactory = $this->createMock(ArgumentFactory::class);
58
        $this->requestStack = $this->createMock(RequestStack::class);
59
        $this->argumentContext = $this->createMock(ArgumentContextInterface::class);
60
        $this->controllerHelper = $this->createMock(ControllerHelperInterface::class);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createMock(\Addik...HelperInterface::class) of type object<PHPUnit\Framework\MockObject\MockObject> is incompatible with the declared type object<Addiks\SymfonyGen...trollerHelperInterface> of property $controllerHelper.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
61
62
        $this->compiler = new ArgumentCompiler(
63
            $this->argumentFactory,
64
            $this->requestStack,
65
            $this->argumentContext,
0 ignored issues
show
$this->argumentContext is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<Addiks\SymfonyGen...gumentContextInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
66
            $this->controllerHelper
0 ignored issues
show
$this->controllerHelper is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<Addiks\SymfonyGen...trollerHelperInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
67
        );
68
    }
69
70
    /**
71
     * @test
72
     * @dataProvider dataProviderForShouldBuildArguments
73
     */
74
    public function shouldBuildArguments($expectedArguments, array $argumentsConfiguration)
75
    {
76
        /** @var Request $request */
77
        $request = $this->createMock(Request::class);
78
79
        $this->requestStack->method('getCurrentRequest')->willReturn($request);
80
81
        $this->argumentContext->expects($this->once())->method('clear');
82
        $this->argumentContext->expects($this->once())->method('set')->with(
83
            $this->equalTo('foo'),
84
            $this->equalTo('bar')
85
        );
86
87
        /** @var Argument $argument */
88
        $argument = $this->createMock(Argument::class);
89
        $argument->method('resolve')->willReturn('dolor');
90
91
        $this->argumentFactory->method('understandsString')->willReturn(true);
92
        $this->argumentFactory->method('createArgumentFromString')->willReturn($argument);
93
        $this->argumentFactory->method('understandsArray')->willReturn(true);
94
        $this->argumentFactory->method('createArgumentFromArray')->willReturn($argument);
95
96
        /** @var mixed $actualArguments */
97
        $actualArguments = $this->compiler->buildArguments(
98
            $argumentsConfiguration,
99
            ['foo' => 'bar']
100
        );
101
102
        $this->assertEquals($expectedArguments, $actualArguments);
103
    }
104
105
    public function dataProviderForShouldBuildArguments(): array
106
    {
107
        return array(
108
            [[], []],
109
            [['lorem' => 'dolor'], ['lorem' => '$ipsum']],
110
            [['lorem' => 'dolor'], ['lorem' => ['$ipsum']]],
111
        );
112
    }
113
114
    /**
115
     * @test
116
     */
117 View Code Duplication
    public function shouldExpectArgumentFactoryToUnderstandString()
118
    {
119
        $this->expectExceptionMessage("Argument 'dolor' could not be understood!");
120
121
        /** @var Request $request */
122
        $request = $this->createMock(Request::class);
123
124
        $this->requestStack->method('getCurrentRequest')->willReturn($request);
125
126
        /** @var Argument $argument */
127
        $argument = $this->createMock(Argument::class);
128
129
        $this->argumentFactory->method('understandsString')->willReturn(false);
130
131
        $this->compiler->buildArguments(['lorem' => 'dolor'], []);
132
    }
133
134
    /**
135
     * @test
136
     */
137 View Code Duplication
    public function shouldExpectArgumentFactoryToUnderstandArray()
138
    {
139
        $this->expectExceptionMessage("Argument 'array(0=>'dolor',)' could not be understood!");
140
141
        /** @var Request $request */
142
        $request = $this->createMock(Request::class);
143
144
        $this->requestStack->method('getCurrentRequest')->willReturn($request);
145
146
        /** @var Argument $argument */
147
        $argument = $this->createMock(Argument::class);
148
149
        $this->argumentFactory->method('understandsString')->willReturn(false);
150
151
        $this->compiler->buildArguments(['lorem' => ['dolor']], []);
152
    }
153
154
    /**
155
     * @test
156
     */
157 View Code Duplication
    public function shouldExpectArgumentToBeArrayOrString()
158
    {
159
        $this->expectExceptionMessage("Arguments must be defined as string, array, bool or null!");
160
161
        /** @var Request $request */
162
        $request = $this->createMock(Request::class);
163
164
        $this->requestStack->method('getCurrentRequest')->willReturn($request);
165
166
        /** @var Argument $argument */
167
        $argument = $this->createMock(Argument::class);
168
169
        $this->argumentFactory->method('understandsString')->willReturn(false);
170
171
        $this->compiler->buildArguments(['lorem' => 3.1415], []);
172
    }
173
174
    /**
175
     * @test
176
     * @dataProvider dataProviderForShouldBuildCallArguments
177
     */
178
    public function shouldBuildCallArguments($expectedArguments, array $parameters, array $argumentsConfiguration)
179
    {
180
        /** @var Request $request */
181
        $request = $this->createMock(Request::class);
182
183
        $this->requestStack->method('getCurrentRequest')->willReturn($request);
184
185
        $this->argumentContext->expects($this->once())->method('clear');
186
        $this->argumentContext->expects($this->once())->method('set')->with(
187
            $this->equalTo('foo'),
188
            $this->equalTo('bar')
189
        );
190
191
        /** @var ReflectionFunctionAbstract $routineReflection */
192
        $routineReflection = $this->createMock(ReflectionFunctionAbstract::class);
193
        $routineReflection->method('getParameters')->willReturn($parameters);
194
195
        /** @var Argument $argument */
196
        $argument = $this->createMock(Argument::class);
197
        $argument->method('resolve')->willReturn('dolor');
198
199
        $this->argumentFactory->method('understandsString')->willReturn(true);
200
        $this->argumentFactory->method('createArgumentFromString')->willReturn($argument);
201
        $this->argumentFactory->method('understandsArray')->willReturn(true);
202
        $this->argumentFactory->method('createArgumentFromArray')->willReturn($argument);
203
204
        /** @var mixed $actualArguments */
205
        $actualArguments = $this->compiler->buildCallArguments(
206
            $routineReflection,
207
            $argumentsConfiguration,
208
            [1 => 'def'],
209
            ['foo' => 'bar']
210
        );
211
212
        $this->assertEquals($expectedArguments, $actualArguments);
213
    }
214
215
    public function dataProviderForShouldBuildCallArguments()
216
    {
217
        /** @var TestCase $testCase */
218
        $testCase = $this;
219
220
        /** @var Closure $buildParameter */
221
        $buildParameter = function (string $name, bool $hasType = false, $parameterTypeName = null) use ($testCase) {
222
223
            /** @var ReflectionType $parameterType */
224
            $parameterType = $testCase->createMock(ReflectionType::class);
225
            $parameterType->method('__toString')->willReturn($parameterTypeName);
226
227
            /** @var ReflectionParameter $parameter */
228
            $parameter = $testCase->createMock(ReflectionParameter::class);
229
            $parameter->method('hasType')->willReturn($hasType);
230
            $parameter->method('getType')->willReturn(is_string($parameterTypeName) ?$parameterType :null);
231
            $parameter->method('getName')->willReturn($name);
232
233
            return $parameter;
234
        };
235
236
        return array(
237
            [[], [], []],
238
            [
239
                ['dolor', 'def'],
240
                [$buildParameter("blah"), $buildParameter("blah")],
241
                [0 => '$ipsum']
242
            ],
243
            [
244
                ['dolor'],
245
                [$buildParameter("blah")],
246
                ['blah' => 'asd']
247
            ],
248
            [
249
                [$this->createMock(Request::class), 'def', "dolor"],
250
                [$buildParameter("blah", true, Request::class), $buildParameter("blah"), $buildParameter("blah")],
251
                [2 => '$ipsum']
252
            ],
253
        );
254
    }
255
256
    /**
257
     * @test
258
     */
259
    public function shouldCatchReflectionException()
260
    {
261
        $this->expectExceptionMessage("Missing argument 'blah' for the call to 'doSomething'!");
262
263
        /** @var Request $request */
264
        $request = $this->createMock(Request::class);
0 ignored issues
show
$request is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
265
266
        /** @var ReflectionFunctionAbstract $routineReflection */
267
        $routineReflection = $this->createMock(ReflectionFunctionAbstract::class);
268
269
        /** @var ReflectionParameter $parameterWithoutDefaultValue */
270
        $parameterWithoutDefaultValue = $this->createMock(ReflectionParameter::class);
271
        $parameterWithoutDefaultValue->method('hasType')->willReturn(false);
272
        $parameterWithoutDefaultValue->method('getName')->willReturn("blah");
273
        $parameterWithoutDefaultValue->method('getDeclaringFunction')->willReturn($routineReflection);
274
        $parameterWithoutDefaultValue->method('getDefaultValue')->will($this->returnCallback(
275
            function () {
276
                throw new ReflectionException("We don't have a default value, bro!");
277
            }
278
        ));
279
280
        $routineReflection->method('getParameters')->willReturn([$parameterWithoutDefaultValue]);
281
        $routineReflection->method('getName')->willReturn("doSomething");
282
283
        $this->compiler->buildCallArguments(
284
            $routineReflection,
285
            [],
286
            [],
287
            []
288
        );
289
    }
290
291
}
292