Skip to content

Commit a013b23

Browse files
bbatscheondrejmirtes
authored andcommitted
Add handling for more complex declaration syntax:
- alias: - overload: - Multiple interface names - Constructor arguments - Method declaration
1 parent 0057dac commit a013b23

File tree

6 files changed

+139
-9
lines changed

6 files changed

+139
-9
lines changed

src/Mockery/Type/MockDynamicReturnTypeExtension.php

+28-7
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,36 @@ public function getTypeFromStaticMethodCall(
4141
return $defaultReturnType;
4242
}
4343

44-
$classType = $scope->getType($methodCall->args[0]->value);
45-
if (!$classType instanceof ConstantStringType) {
46-
return $defaultReturnType;
44+
$types = [$defaultReturnType];
45+
foreach ($methodCall->args as $arg) {
46+
$classType = $scope->getType($arg->value);
47+
if (!$classType instanceof ConstantStringType) {
48+
continue;
49+
}
50+
51+
$value = $classType->getValue();
52+
if (substr($value, 0, 6) === 'alias:') {
53+
$value = substr($value, 6);
54+
}
55+
if (substr($value, 0, 9) === 'overload:') {
56+
$value = substr($value, 9);
57+
}
58+
if (substr($value, -1) === ']' && strpos($value, '[') !== false) {
59+
$value = substr($value, 0, strpos($value, '['));
60+
}
61+
62+
if (strpos($value, ',') !== false) {
63+
$interfaceNames = explode(',', str_replace(' ', '', $value));
64+
} else {
65+
$interfaceNames = [$value];
66+
}
67+
68+
foreach ($interfaceNames as $name) {
69+
$types[] = new ObjectType($name);
70+
}
4771
}
4872

49-
return TypeCombinator::intersect(
50-
$defaultReturnType,
51-
new ObjectType($classType->getValue())
52-
);
73+
return TypeCombinator::intersect(...$types);
5374
}
5475

5576
}

tests/Mockery/Baz.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Mockery;
4+
5+
interface Baz
6+
{
7+
8+
public function doFoo(): ?string;
9+
10+
}

tests/Mockery/Buzz.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Mockery;
4+
5+
interface Buzz
6+
{
7+
8+
public function doBuzz(): ?string;
9+
10+
}

tests/Mockery/Foo.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22

33
namespace PHPStan\Mockery;
44

5-
class Foo
5+
class Foo implements Baz
66
{
77

8+
/** @var bool */
9+
private $optional;
10+
11+
public function __construct(bool $optional = true)
12+
{
13+
$this->optional = $optional;
14+
}
15+
816
public function doFoo(): ?string
917
{
10-
if (rand(0, 1) === 0) {
18+
if ($this->optional) {
1119
return null;
1220
}
1321

tests/Mockery/IsolatedMockeryTest.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Mockery;
4+
5+
/**
6+
* @runTestsInSeparateProcesses
7+
*/
8+
class IsolatedMockeryTest extends \PHPUnit\Framework\TestCase
9+
{
10+
11+
public function testAliasMock(): void
12+
{
13+
$fooMock = \Mockery::mock('alias:' . Foo::class);
14+
$this->requireFoo($fooMock);
15+
16+
$fooMock->shouldReceive('doFoo')->andReturn('bar');
17+
self::assertSame('bar', $fooMock->doFoo());
18+
}
19+
20+
public function testOverloadMock(): void
21+
{
22+
$fooMock = \Mockery::mock('overload:' . Foo::class);
23+
$this->requireFoo($fooMock);
24+
25+
$fooMock->shouldReceive('doFoo')->andReturn('bar');
26+
self::assertSame('bar', $fooMock->doFoo());
27+
}
28+
29+
private function requireFoo(Foo $foo): void
30+
{
31+
}
32+
33+
}

tests/Mockery/MockeryTest.php

+48
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,56 @@ public function testMockFromProperty(): void
4646
self::assertSame('foo', $this->fooMock->doFoo());
4747
}
4848

49+
public function testMockInterface(): void
50+
{
51+
$interfaceMock = \Mockery::mock(Baz::class, Buzz::class);
52+
$this->requireBaz($interfaceMock);
53+
$this->requireBuzz($interfaceMock);
54+
55+
$interfaceMock->shouldReceive('doFoo')->andReturn('bar');
56+
self::assertSame('bar', $interfaceMock->doFoo());
57+
}
58+
59+
public function testMockFooWithInterfaces(): void
60+
{
61+
$fooMock = \Mockery::mock(Foo::class, Baz::class . ', ' . Buzz::class);
62+
$this->requireFoo($fooMock);
63+
$this->requireBaz($fooMock);
64+
$this->requireBuzz($fooMock);
65+
66+
$fooMock->shouldReceive('doFoo')->andReturn('bar');
67+
self::assertSame('bar', $fooMock->doFoo());
68+
}
69+
70+
public function testMockWithConstructorArgs(): void
71+
{
72+
$fooMock = \Mockery::mock(Foo::class, [true]);
73+
$this->requireFoo($fooMock);
74+
75+
$fooMock->shouldReceive('doFoo')->andReturn('bar');
76+
self::assertSame('bar', $fooMock->doFoo());
77+
}
78+
79+
public function testMockWithInterfaceAndConstructorArgs(): void
80+
{
81+
$fooMock = \Mockery::mock(Foo::class, Buzz::class, [true]);
82+
$this->requireFoo($fooMock);
83+
$this->requireBuzz($fooMock);
84+
85+
$fooMock->shouldReceive('doFoo')->andReturn('bar');
86+
self::assertSame('bar', $fooMock->doFoo());
87+
}
88+
4989
private function requireFoo(Foo $foo): void
5090
{
5191
}
5292

93+
private function requireBaz(Baz $baz): void
94+
{
95+
}
96+
97+
private function requireBuzz(Buzz $buzz): void
98+
{
99+
}
100+
53101
}

0 commit comments

Comments
 (0)