Vscode-intelephense: PHPUnit MockObject not matching expected type

Created on 26 Sep 2019  路  20Comments  路  Source: bmewburn/vscode-intelephense

When writing tests intelephense gives error on PHPUnit MockObject.

Expected type 'PDO'. Found 'PHPUnit\Framework\MockObject\MockObject'.intelephense(10006)

Is there a way to get around this, or need to wait for #204?

bug

Most helpful comment

getMock returns MockObject so you need to annotate the variable assignment to override this.

/** @var \PDO&\PHPUnit\Framework\MockObject\MockObject $pdo */
 $pdo = $this->getMockBuilder(\PDO::class)
                ->disableOriginalConstructor()
                ->setMethods(['prepare'])
                ->getMock();

All 20 comments

It would be nice if you could provide example code which results in error, without that will be hard to find potential bugs.

I am mocking a PDO object so I can execute the logic test without actually hitting a database.

    $statement = $this->getMockBuilder(\PDOStatement::class)
                    ->setMethods(['execute'])
                    ->getMock();

    $statement->expects($this->once())
                ->method('execute')
                ->willReturn(false);

    $pdo = $this->getMockBuilder(\PDO::class)
                ->disableOriginalConstructor()
                ->setMethods(['prepare'])
                ->getMock();

    $pdo->method('prepare')->willReturn($statement);

    $storage = new MyStorage\Pdo($pdo);

The MyStorage\Pdo class on the last line above expects a PDO object, which PHPUnit MockObject satisfies as far as php is concerned, but Intelephense complains.

getMock returns MockObject so you need to annotate the variable assignment to override this.

/** @var \PDO&\PHPUnit\Framework\MockObject\MockObject $pdo */
 $pdo = $this->getMockBuilder(\PDO::class)
                ->disableOriginalConstructor()
                ->setMethods(['prepare'])
                ->getMock();

Thank you

Sorry to post here, not sure if it's worth opening a new ticket. My use case is quite similar but still having issues.

Long story short I was looking something like this to be picked by Intelephense but doesn't work:

// This annotation is ignored by Intelephense
/** @var \PDO&MockObject */
$this->pdo = $this->getMock(\PDO::class);

Please see below how this snipped is being used.

use PHPUnit\Framework\MockObject\MockObject;

class myClass extends TestCase
{
    // This hinting doesn't make any difference
    /** @var \PDO&MockObject */
    private $pdo;

    private $service;

    protected function setUp()
    {
        // I've tried this but doesn't work either. Looks like
        // Intelephense only looks at the return value of getMock()
        /** @var \PDO&MockObject */
        $this->pdo = $this->getMock(\PDO::class);

        $this->someService = new Service(
            // Intelephense complains here that pdo is a Mock and is not expected
            $this->con
        );
    }

    public function someTest()
    {
        // this is how pdo mock is used. That's why I promoted it to a class property
        // instead of a local variable, I usually have many test methods that will make
        // use of the mocked object.
        $this->pdo->expects($this->once())...
        $this->service->doSomethingThatDependsOnPDO();
    }

Thank you!

I'm not using phpunit, so I've made up example code. However there is some kind of bug in handling override. @bmewburn can you take a look?

Edit: It is already included in phpunit https://github.com/sebastianbergmann/phpunit/blob/master/.phpstorm.meta.php and apparently doesn't work.


Mentioned example code:

<?php

namespace PHPSTORM_META{
    override(\TestCase::getMock(0), map([
        '' => '@&\MockObject',
    ]));
}

namespace {

    class MockObject
    {
        /**
         * Expects whatever
         * @return stdClass
         */
        public function expects(){}
    }

    class TestCase
    {
        public function getMock(){}
    }

    class myClass extends TestCase
    {

        public function someTest()
        {
            // here $pdo correctly type-hinted
            $pdo = $this->getMock(\PDO::class);
            // $me is mixed, and expects() have no hover
            $me = $pdo->expects();
        }
    }
}

@KapitanOczywisty I've opened #968 for that.

@leftdevel I modified your example a little to remove other errors but I cannot reproduce the problem. $pdo is of the type in the annotation in my tests. Please open a new ticket with a minimal reproducible example if you are still having problems.

<?php

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

function expectsPdo(PDO $pdo) { }

class myClass extends TestCase
{
    // This hinting doesn't make any difference
    /** @var \PDO&MockObject */
    private $pdo;

    protected function setUp()
    {
        // I've tried this but doesn't work either. Looks like
        // Intelephense only looks at the return value of getMock()
        /** @var \PDO&MockObject */
        $this->pdo = $this->getMock(\PDO::class);
        expectsPdo($this->pdo);
    }

    public function someTest()
    {
        // this is how pdo mock is used. That's why I promoted it to a class property
        // instead of a local variable, I usually have many test methods that will make
        // use of the mocked object.
        $this->pdo->expects($this->once())->
    }

    /** @return MockObject */
    function getMock($name) {
        return $this->createMock($name);
    }
}

Screenshot from 2020-01-21 18-22-17

Hi @bmewburn, thanks so much for taking the time to check. I really appreciate it. I'll copy and paste your changes and see what's going on.

Hello. Sorry I am not sure if there is another issue here or not. I followed the additional comments above and other issues, not sure if what I am seeing is supposed to be fixed in 1.3.8 or is a new problem.

        /** @var \PDO&\PHPUnit\Framework\MockObject\MockObject $pdo */
        $pdo = $this->getMockBuilder(\PDO::class)
            ->disableOriginalConstructor()
            ->setMethods(['prepare'])
            ->getMock();
        $pdo->expects($this->once());

The above code no longer works for me and gives Undefined method 'expects'.intelephense(1013) at version 1.3.11. Removing the \PDO& part makes the error go away, albeit also loosing code suggestions for removed object.

@mta59066 , I'm unable to reproduce this in 1.3.11 with the above snippet. Can you please add a complete example that reproduces the issue?

Thank you @bmewburn. The example code above is exactly what I am using to produce the issue, on a mac with vscode 1.42.1, php 7.4.2 and phpcs 3.5.4

image

image

Version: 1.42.1
Commit: c47d83b293181d9be64f27ff093689e8e7aed054
Date: 2020-02-11T14:44:27.652Z
Electron: 6.1.6
Chrome: 76.0.3809.146
Node.js: 12.4.0
V8: 7.6.303.31-electron.0
OS: Darwin x64 18.7.0

@mta59066 , I can't reproduce the issue with that code. Can you try reindexing? ctrl + shift + p -> Index workspace

@bmewburn Same after Index workspace. I downgraded version by version, it goes away on 1.3.6 and comes back on 1.3.7. I tried with or without premium license key, but that didn't make a difference.

@mta59066 what type is shown when you hover on $pdo on this line.

$pdo->expects($this->once());

@bmewburn It shows @var \PDO&\PHPUnit\Framework\MockObject\MockObject $pdo. Everything seems to work except the undefined method error.

image

image

@bmewburn is there any additional information I can provide on this?

image
So to get rid of the error message I have to create variables first?

Hello @bmewburn here is something that will hopefully allow you to replicate this problem.

Open a new file and just put the below code exactly as it is in it. Removing the testOther() method or even commenting out the $oth->other = true; line makes the problem go away. Just switched computers and created this on a new install.

<?php

class Test extends \PHPUnit\Framework\TestCase
{
    public function testOther()
    {
        $oth = $this->getMockBuilder('Other')->getMock();
        $oth->other = true;
    }

    function testProblem() {
        /** @var \PDO&\PHPUnit\Framework\MockObject\MockObject $pdo */
        $pdo = $this->getMockBuilder(\PDO::class)
            ->disableOriginalConstructor()
            ->setMethods(['prepare'])
            ->getMock();
        $pdo->expects($this->once());
    }
}

Here is what it looks like

image

image

Hello @bmewburn, can you relabel this issue as a bug if I have been able to provide you the necessary information to reproduce it?

@mta59066 , yes thanks for the example

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghnp5 picture ghnp5  路  3Comments

dgunay picture dgunay  路  3Comments

usrnm-nk picture usrnm-nk  路  3Comments

superadmini picture superadmini  路  4Comments

zlianon picture zlianon  路  3Comments