Coder Social home page Coder Social logo

phpstan-mockery's Introduction

PHPStan Mockery extension

Build Latest Stable Version License

This extension provides the following features:

  • Interprets Foo|\Mockery\MockInterface in phpDoc so that it results in an intersection type instead of a union type.
  • Mockery::mock() and Mockery::spy() return an intersection type (see the detailed explanation of intersection types) so that the returned object can be used as both the mock object and the mocked class object.
  • shouldReceive(), allows() and expects() methods can be called on the mock object and they work as expected.

Installation

To use this extension, require it in Composer:

composer require --dev phpstan/phpstan-mockery

If you also install phpstan/extension-installer then you're all set!

Manual installation

If you don't want to use phpstan/extension-installer, include extension.neon in your project's PHPStan config:

includes:
    - vendor/phpstan/phpstan-mockery/extension.neon

phpstan-mockery's People

Contributors

adaamz avatar bbatsche avatar dependabot[bot] avatar fancyweb avatar herndlm avatar kocal avatar localheinz avatar lookyman avatar martinssipenko avatar michaeldemeyer avatar msvrtan avatar ondrejmirtes avatar renovate-bot avatar renovate[bot] avatar ruudk avatar simpod avatar staabm avatar tomasvotruba avatar villfa avatar wimski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

phpstan-mockery's Issues

Passing an array as $methodNames argument in LegacyMockerInterface::shouldReceive() results in error

Setup

phpstan/phpstan-mockery 0.12.7
phpstan/phpstan-phpunit 0.12.16

# phpstan.neon.dist
includes:
    - ./vendor/phpstan/phpstan-mockery/extension.neon
    - ./vendor/phpstan/phpstan-phpunit/extension.neon

Issue

No errors

Using the most basic shouldReceive syntax produces no PHPStan errors.

/** @var MyClass|MockInterface */
$myMock = Mockery::mock(MyClass::class);
$myMock
    ->shouldReceive('someMethod')
    ->andReturn('resultString');

PHPStan error

Using the array notation for shouldReceive gives the following error:

Parameter #1 ...$methodNames of method Mockery\LegacyMockInterface::shouldReceive() expects string, array<string, string> given.
/** @var MyClass|MockInterface */
$myMock = Mockery::mock(MyClass::class);
$myMock->shouldReceive([
    'someMethod' => 'resultString',
]);

http://docs.mockery.io/en/latest/reference/expectations.html

Infer mock type when defined in setUp

I'm having this test:

use Mockery\MockInterface;

class TypedPropertyTest extends \PHPUnit\Framework\TestCase
{

	private MockInterface $fooMock;

	protected function setUp(): void
	{
		$this->fooMock = \Mockery::mock(Foo::class);
	}

	public function testAllows(): void
	{
		$bar = new Bar($this->fooMock);

		$this->fooMock
			->allows('doFoo')
			->with()
			->andReturn('foo');

		self::assertSame('foo', $bar->doFoo());
	}

}

But PHPStan reports issues:

 ------ ----------------------------------------------------------------------------------------------------------------------
  Line   tests/Mockery/TypedPropertyTest.php
 ------ ----------------------------------------------------------------------------------------------------------------------
  19     Parameter #1 $foo of class PHPStan\Mockery\Bar constructor expects PHPStan\Mockery\Foo, Mockery\MockInterface given.
  21     Call to an undefined method Mockery\ExpectationInterface|Mockery\HigherOrderMessage|Mockery\MockInterface::with().
 ------ ----------------------------------------------------------------------------------------------------------------------

The only way to solve this is by adding a PHPDoc:

/** @var MockInterface|Foo */
private MockInterface $fooMock;

It would be great if it could read the Foo type from the setUp method and intersect that with MockInterface type.

@ondrejmirtes Is that possible? Do you maybe have an example that's similar? I'd like to create a PR to address it.

Require unit tests to call `Mockery::close()` or use `MockeryPHPUnitIntegration` trait

When using Mockery in your PHPUnit tests, it's important to make sure you call Mockery::close() on tearDown(). Or use the MockeryPHPUnitIntegration trait for that.

This is often forgotten, and that leads to weird situations.

It would be great if the extension could detect this, and give an error instructing you what to do to solve it.

Some people might extend from a custom base class where this is done for them, for those this feature should be turned off, or configured so that it also supports a custom base class.

New release for phpstan 0.12?

I noticed the master branch is prepared to use phpstan 0.12. Any update on when we can expect a release so that we can use this with the newest version of phpstan?

Call to method PHPStan\Mockery\Type\Expects::never() on a separate line has no effect.

The offending code looks like this:

        $gitlab = $this->mock(GitLabManager::class);
        $gitlab
            ->expects('repositories')
            ->never();

Reformatting does not change the error:

        $gitlab = $this->mock(GitLabManager::class);
        $gitlab->expects('repositories')->never();

My dependencies are the following:

        "mockery/mockery": "^1.3.1",
        "phpstan/phpstan-mockery": "^0.12.3",
        "nunomaduro/larastan": "^0.5.0",

Am i doing something wrong?

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

composer
composer.json
  • php ^7.2 || ^8.0
  • phpstan/phpstan ^1.10
  • mockery/mockery ^1.6.11
  • nikic/php-parser ^4.13.0
  • php-parallel-lint/php-parallel-lint ^1.2
  • phpstan/phpstan-phpunit ^1.0
  • phpstan/phpstan-strict-rules ^1.0
  • phpunit/phpunit ^9.5
github-actions
.github/workflows/build.yml
  • actions/checkout v4
  • shivammathur/setup-php v2
  • actions/checkout v4
  • actions/checkout v4
  • shivammathur/setup-php v2
  • actions/checkout v4
  • shivammathur/setup-php v2
  • actions/checkout v4
  • shivammathur/setup-php v2
.github/workflows/create-tag.yml
  • actions/checkout v4
  • WyriHaximus/github-action-get-previous-tag v1
  • WyriHaximus/github-action-next-semvers v1
  • rickstaa/action-create-tag v1
  • rickstaa/action-create-tag v1
.github/workflows/lock-closed-issues.yml
  • dessant/lock-threads v5
.github/workflows/release-toot.yml
  • cbrgm/mastodon-github-action v2
.github/workflows/release-tweet.yml
  • Eomm/why-don-t-you-tweet v1
.github/workflows/release.yml
  • actions/checkout v4
  • metcalfc/changelog-generator v4.3.1
  • actions/create-release v1

  • Check this box to trigger a request for Renovate to run again on this repository

Calling PHPStan\Mockery\Type\Expects::with() is not covered by backward compatibility promise.

When upgrading from phpstan/phpstan 0.12.88 to 0.12.89 I'm getting a lot of errors like this:

Calling PHPStan\Mockery\Type\Expects::with() is not covered by backward compatibility promise. The
         method might change in a minor PHPStan version.
         ๐Ÿ’ก If you think it should be covered by backward compatibility promise, open a discussion:
            https://github.com/phpstan/phpstan/discussions

            See also:
            https://phpstan.org/developing-extensions/backward-compatibility-promise

I see that a fix has been pushed for phpstan-phpunit. Should the same type of fix be done here as well?

Some currently unsupported cases

Some expectation methods

$mock = Mockery::mock(Something::class);
$mock
    ->shouldReceive('foo')
    ->andReturn(null)
    ->shouldReceive('bar');

Currently, the second shouldReceive call will trigger this error: Call to an undefined method Mockery\Expectation::shouldReceive().

Partial mocks

$mock = Mockery::mock('Foo\Bar[method1,method2]');

Currently, $mock is assumed to be of type Foo\Bar[method1,method2]

Methods returning Mock

$mock = Mockery::mock(Bar::class)->shouldDeferMissing();

Currently, methods returning Mockery\Mock will just return that, instead of an intersection type.

PHPStan reports error when using alternative syntax of shouldReceive method

Hi
I am using PHPStan version 0.12.2 and phpstan/phpstan-mockery version 0.12.3.
My code looks like this:

$validatorMock = \Mockery::spy('alias:' . Validator::class);
$validatorMock->shouldReceive(['isValueValid' => false]);

I am mocking Validator class, which has static method isValueValid.
And PHPStan reports error:

Parameter #1 ...$methodNames of method Mockery\LegacyMockInterface::shouldReceive() expects string, array<string, false> given.

However when I change the syntax to
$validatorMock->shouldReceive('isValueValid')->andReturn(false);
PHPStan does not report an error then. Must be this problem fixed in this package or not?

Incorrect doc blocks for shouldHaveReceived and shouldNotHaveReceived

The doc blocks for the shouldHaveReceived and shouldNotHaveReceived methods in

public function shouldHaveReceived($method, $args = null);
are incorrect.

These methods can also accept a Closure as the second argument, as you can see here: https://github.com/mockery/mockery/blob/master/library/Mockery/LegacyMockInterface.php#L87.

Currently, providing a closure as the second argument will trigger a PHPStan error.

 Parameter #2 $args of method Mockery\LegacyMockInterface::shouldNotHaveReceived() expects array<string>|null, Closure given.

Support makePartial()

It is possible to create a partial mock like this:

class SomeTest extends \PHPUnit\Framework\TestCase {
    public function createMock(): MyClass
    {
        return Mockery::mock(MyClass::class)->makePartial();
    }
}

However, phpstan will complain that Method SomeTest::createMock() should return MyClass but returns Mockery\Mock..

Call to an undefined method xxx::shouldReceive()

I have a ParentTestCase that sets up a few common variables, and these are then failing in PHPStan with the message:

  24     Call to an undefined method Mockery\LegacyMockInterface|Psr\Log\LoggerInterface::shouldReceive().  

ActionTestCase.php

<?php
declare(strict_types=1);

namespace Tests\Action;

use DI\ContainerBuilder;
use Exception;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\LegacyMockInterface;
use Mockery\MockInterface;
use Psr\Log\LoggerInterface;
use Slim\App;
use Slim\Factory\AppFactory;

class ActionTestCase extends MockeryTestCase
{
    /**
     * @var LoggerInterface|LegacyMockInterface|MockInterface
     */
    protected $logger;

    protected function setUp(): void
    {
        parent::setUp();
        $this->logger = Mockery::mock(LoggerInterface::class);
    }

    /**
     * @return App
     * @throws Exception
     */
    protected function getAppInstance(): App
    {
        // Instantiate PHP-DI ContainerBuilder
        $containerBuilder = new ContainerBuilder();

        // Set up settings
        $settings = require __DIR__ . '/../../app/settings.php';
        $settings($containerBuilder);

        // Set up dependencies
        $dependencies = require __DIR__ . '/../../app/dependencies.php';
        $dependencies($containerBuilder);

        // Set up repositories
        $repositories = require __DIR__ . '/../../app/repositories.php';
        $repositories($containerBuilder);

        // Build PHP-DI Container instance
        $container = $containerBuilder->build();
        $container->set(LoggerInterface::class, $this->logger);

        // Instantiate the app
        AppFactory::setContainer($container);
        $app = AppFactory::create();

        // Register routes
        $routes = require __DIR__ . '/../../app/routes.php';
        $routes($app);

        $app->addBodyParsingMiddleware();
        $app->addRoutingMiddleware();

        return $app;
    }
}

AuthLinkActionTest.php

<?php

declare(strict_types=1);

namespace Tests\Action\Auth;

use Tests\Action\ActionTestCase;

class AuthLinkActionTest extends ActionTestCase
{
    /**
     * @throws Exception
     */
    public function testLinkNoParamsLogsAndRedirects(): void
    {
        $app = $this->getAppInstance();

        $this->logger->shouldReceive('critical')->once()
            ->with('Authentication error: Could not link client to application')
            ->andReturnSelf();

        $request = $this->createRequest('GET', '/auth/link');
        $response = $app->handle($request);
        self::assertEquals(302, $response->getStatusCode());
        self::assertEquals('/', $response->getHeaderLine('Location'));
    }
}

The same thing happens if I move the setUp function to the AuthLinkActionTest class.

I have found if I do the following, it works, but that defeats the point of having a "common" variable

<?php

declare(strict_types=1);

namespace Tests\Action\Auth;

use Tests\Action\ActionTestCase;

class AuthLinkActionTest extends ActionTestCase
{
    /**
     * @throws Exception
     */
    public function testLinkNoParamsLogsAndRedirects(): void
    {
        $app = $this->getAppInstance();

        $this->logger = Mockery::mock(LoggerInterface::class);
        $this->logger->shouldReceive('critical')->once()
            ->with('Authentication error: Could not link client to application')
            ->andReturnSelf();

        $request = $this->createRequest('GET', '/auth/link');
        $response = $app->handle($request);
        self::assertEquals(302, $response->getStatusCode());
        self::assertEquals('/', $response->getHeaderLine('Location'));
    }
}

Expects Mockery\Expectation, PHPStan\Mockery\Type\Expects given.

$mock = Mockery::mock(MyClass::class);
$expectCall = $mock->expect('call');

self::assertSth($expectCall);

...

private function assertSth(Expectation $expectCall) : void
{
    $expectCall->once();
}

Gives

Parameter #1 $expectCall of method Ns\ClassTest::assertSth() expects Mockery\Expectation, PHPStan\Mockery\Type\Expects given.

v0.11.3

Missing mock type information for Mockery\Expectation::getMock()

Mockery::mock() returns a Mockery\MockInterface. MockInterface::shouldReceive() et al returns an Mockery\Expectation, which in turn has a getMock() method to return the MockInterface the Expectation belongs to.

The problem is that phpstan says the return type of Expectation::getMock() is Mockery\MockInterface with no type information on what was originally mocked. So for example

interface A {
    foo(): void
}

function getMock(): A
{
    return Mockery::mock(A::class)
        ->shouldReceive('foo')
        ->getMock();
}

will cause the following phpstan error: Function getMock() should return A but returns Mockery\MockInterface.

Of course the code in getMock() can be rewritten to not be fluent so that the return value of Mockery::mock() is returned instead, however in many cases I mock objects inline in some other expression and when I do that it's really helpful to be able to use the fluent way of writing the expectations.

Is it possible to change the type of the return value of Mockery\Expectation::getMock() to include the type information of the original mock? With generics I guess it should be something like Mockery::mock(T): MockInterface<T>, MockInterface<T>::shouldReceive(): Expectation<T> and Expectation<T>::getMock(): MockInterface<T>.

Call to protected method xxx()

class Foo
{
    protected function bar()
    {
        echo 'bar';
    }
}

$mock = \Mockery::mock(Foo::class);
$mock
    ->shouldAllowMockingProtectedMethods()
    ->makePartial();

$mock->bar();

Call to protected method bar()

phpstan/phpstan v0.12.25
phpstan/phpstan-mockery v0.12.5

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.