vscode-intelephense icon indicating copy to clipboard operation
vscode-intelephense copied to clipboard

Intelephense thinks Mockery::mock() returns a string

Open jtowe1 opened this issue 3 years ago • 11 comments

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 Screen Shot 2021-05-07 at 9 35 12 AM

Screen Shot 2021-05-07 at 9 35 20 AM Screen Shot 2021-05-07 at 9 44 55 AM

Platform and version Intelephense 1.7.1, MacOS 11.3, VSCode 1.56.0

jtowe1 avatar May 07 '21 13:05 jtowe1

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.

bmewburn avatar May 07 '21 22:05 bmewburn

Oh wow, I was unaware that's how it's handled. That seems kinda crazy

jtowe1 avatar May 10 '21 16:05 jtowe1

@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?)

Ilyes512 avatar Jun 14 '21 16:06 Ilyes512

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

KapitanOczywisty avatar Jul 15 '21 08:07 KapitanOczywisty

Is this issue already resolved? That is really annoying to have that 'string' error message...

atgote avatar Jan 03 '22 16:01 atgote

And when I have a method that expects the type of the mocked class? How do I dynamically fix it? image

vitorhps avatar Mar 30 '22 16:03 vitorhps

@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 avatar Aug 08 '22 21:08 Sectimus

@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 avatar Aug 08 '22 22:08 KapitanOczywisty

@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?

Sectimus avatar Aug 09 '22 10:08 Sectimus

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'.

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 between MockInterface 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 avatar Aug 09 '22 10:08 KapitanOczywisty

@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!

Sectimus avatar Aug 09 '22 10:08 Sectimus

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) { }
}

bmewburn avatar Jan 14 '23 05:01 bmewburn