vscode-intelephense
vscode-intelephense copied to clipboard
Intelephense thinks Mockery::mock() returns a string
Describe the bug
Intelephense thinks that Mockery::mock() returns a string, but it actually returns a MockInterface. This causes subsequent calls on the mock object to show Expected type 'object'. Found 'string'.
A similar issue was opened for PHPUnit's createMock: https://github.com/bmewburn/vscode-intelephense/issues/1652
To Reproduce
public function testTesterTest()
{
$mock = \Mockery::mock(Contract::class);
$mock
->shouldReceive('methodCall')
->andReturn(1);
}
Expected behavior Intelephense should know that Mockery::mock() returns a MockInterface.
Screenshots
Platform and version Intelephense 1.7.1, MacOS 11.3, VSCode 1.56.0
IMO this is a bug with how mockery declares metadata. https://github.com/mockery/mockery/blob/master/.phpstorm.meta.php
override(\Mockery::mock(0), type(0));
The type of the expression Contract::class
is string
.
Compare this to how phpunit declares this. https://github.com/sebastianbergmann/phpunit/blob/master/.phpstorm.meta.php
override(
\PHPUnit\Framework\TestCase::createMock(0),
map([
'@&\PHPUnit\Framework\MockObject\MockObject',
])
);
Clearly phpstorm is ok with the mockery overrides. I'll look into using the class name instead here but IMO it makes no sense and it also means that the MockInterface
methods won't be found.
Oh wow, I was unaware that's how it's handled. That seems kinda crazy
@bmewburn
I would love for this to get solved. It gets very tiresome to have to add an @var
declaration for every mock I create.
I am not familiar with how the metadata declarations etc work, but if you think it might be something that should be fixed at Mockery's end then maybe it should? I have created an Mockery issue here... not sure if there is enough info here for mockery to solve this (potentially?)
There is PR to fix this, but intelephense works only with @
, but not with $0
, so action from @bmewburn still will be required. https://github.com/mockery/mockery/pull/1126
Is this issue already resolved? That is really annoying to have that 'string' error message...
And when I have a method that expects the type of the mocked class? How do I dynamically fix it?
@vitorhps I know i'm a few months late to the party, though for anyone that appears here looking for an answer - you would have to use @var annotations using the |
operator to indicate to the IDE that the variable could be an instance of either of two classes.
<?php
/**
* @var CategoryRepositoryInterface|MockInterface $mockRepository
*/
$mockRepository = Mockery::mock(stdClass::class, CategoryRepositoryInterface::class);
$mockRepository->shouldRecieve('paginate')->andReturn($mockPagination);
$useCase = new ListCategoriesUseCase($mockRepository)
@Sectimus Or you can add .phpstorm.meta.php
file to own project, to have project-wide solution:
<?php
namespace PHPSTORM_META;
override(\Mockery::mock(0), map(["" => "@&\Mockery\MockInterface"]));
override(\Mockery::spy(0), map(["" => "@&\Mockery\MockInterface"]));
override(\Mockery::namedMock(0), map(["" => "@&\Mockery\MockInterface"]));
override(\Mockery::instanceMock(0), map(["" => "@&\Mockery\MockInterface"]));
override(\mock(0), map(["" => "@&\Mockery\MockInterface"]));
override(\spy(0), map(["" => "@&\Mockery\MockInterface"]));
override(\namedMock(0), map(["" => "@&\Mockery\MockInterface"]));
For PHPUnit:
<?php
namespace PHPSTORM_META;
override(
\PHPUnit\Framework\TestCase::createMock(0),
map(["" => "@&\PHPUnit\Framework\MockObject\MockObject"])
);
override(
\PHPUnit\Framework\TestCase::createStub(0),
map(["" => "@&\PHPUnit\Framework\MockObject\Stub"])
);
override(
\PHPUnit\Framework\TestCase::createConfiguredMock(0),
map(["" => "@&\PHPUnit\Framework\MockObject\MockObject"])
);
override(
\PHPUnit\Framework\TestCase::createPartialMock(0),
map(["" => "@&\PHPUnit\Framework\MockObject\MockObject"])
);
override(
\PHPUnit\Framework\TestCase::createTestProxy(0),
map(["" => "@&\PHPUnit\Framework\MockObject\MockObject"])
);
override(
\PHPUnit\Framework\TestCase::getMockForAbstractClass(0),
map(["" => "@&\PHPUnit\Framework\MockObject\MockObject"])
);
@KapitanOczywisty I've tried many different implementations of the .phpstorm.meta.php
file. Although Intelephense does state that it reads and supports these, I am still receiving Expected type 'object'. Found 'string'
. Interestingly PHP Tools seems to deal with Mockery::mock()
without issue, no magic file nor annotations needed. It even implies the return of a union between MockInterface
as well as the class you are mocking for code completion and type hinting.
I wonder if whatever implementation they have can be utilised here for Intelephense?
I've tried many different implementations of the
.phpstorm.meta.php
file. Although Intelephense does state that it reads and supports these, I am still receivingExpected type 'object'. Found 'string'
.
I'm using these personally, so it works. You may need to check if file isn't excluded or try command "Intelephense: Index workspace".
Interestingly PHP Tools seems to deal with
Mockery::mock()
without issue, no magic file nor annotations needed. It even implies the return of a union betweenMockInterface
as well as the class you are mocking for code completion and type hinting.
PHPStorm makes union from overridden and return type, not sure if this was changed at some point, but Intelephense implements this as replacement. I know that intelephense author is aware of that, but seems that he didn't get around to fix it.
And technically, this should be an intersection, but type checks are not strict in IDE, so it works..
@KapitanOczywisty the magic was in the command: "Intelephense: Index workspace" - I assumed this would index automatically when opening a new workspace, apparently not. When I run that command after opening a new workspace all works as it should!
As well as the workarounds described in this thread it is possible now to write some stubs using @template
format in intelephense 1.9+ and add them to your workspace.
for example
https://github.com/mockery/mockery/blob/0ca8e28ea867089e2798178a06c611c01db6d230/library/Mockery.php#L115
class Mockery {
/**
* Static shortcut to \Mockery\Container::mock().
*
* @template T
* @param class-string<T> $arg
* @return \Mockery\MockInterface&T
*/
public static function mock($arg) { }
}