mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Merge pull request #289 from pestphp/feat/mock
feat: adds built-in mocking
This commit is contained in:
24
src/Exceptions/MissingDependency.php
Normal file
24
src/Exceptions/MissingDependency.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a new instance of missing dependency.
|
||||||
|
*/
|
||||||
|
public function __construct(string $feature, string $dependency)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('The feature "%s" requires "%s".', $feature, $dependency));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Datasets;
|
use Pest\Datasets;
|
||||||
|
use Pest\Mock;
|
||||||
use Pest\PendingObjects\AfterEachCall;
|
use Pest\PendingObjects\AfterEachCall;
|
||||||
use Pest\PendingObjects\BeforeEachCall;
|
use Pest\PendingObjects\BeforeEachCall;
|
||||||
use Pest\PendingObjects\TestCall;
|
use Pest\PendingObjects\TestCall;
|
||||||
@ -104,3 +105,15 @@ function afterAll(Closure $closure): void
|
|||||||
{
|
{
|
||||||
TestSuite::getInstance()->afterAll->set($closure);
|
TestSuite::getInstance()->afterAll->set($closure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!function_exists('mock')) {
|
||||||
|
/**
|
||||||
|
* Creates a new mock with the given class or object.
|
||||||
|
*
|
||||||
|
* @param string|object $object
|
||||||
|
*/
|
||||||
|
function mock($object): Mock
|
||||||
|
{
|
||||||
|
return new Mock($object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
76
src/Mock.php
Normal file
76
src/Mock.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Mockery;
|
||||||
|
use Pest\Exceptions\MissingDependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \Mockery\MockInterface
|
||||||
|
*/
|
||||||
|
final class Mock
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The object being mocked.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
*
|
||||||
|
* @var \Mockery\MockInterface|\Mockery\LegacyMockInterface
|
||||||
|
*/
|
||||||
|
private $mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new mock instance.
|
||||||
|
*
|
||||||
|
* @param string|object $object
|
||||||
|
*/
|
||||||
|
public function __construct($object)
|
||||||
|
{
|
||||||
|
if (!class_exists(Mockery::class)) {
|
||||||
|
throw new MissingDependency('Mocking', 'mockery/mockery');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mock = Mockery::mock($object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define mock expectations.
|
||||||
|
*
|
||||||
|
* @param mixed ...$methods
|
||||||
|
*
|
||||||
|
* @return \Mockery\MockInterface|\Mockery\LegacyMockInterface
|
||||||
|
*/
|
||||||
|
public function expect(...$methods)
|
||||||
|
{
|
||||||
|
foreach ($methods as $method => $expectation) {
|
||||||
|
/* @phpstan-ignore-next-line */
|
||||||
|
$method = $this->mock
|
||||||
|
->shouldReceive((string) $method)
|
||||||
|
->once();
|
||||||
|
|
||||||
|
if (!is_callable($expectation)) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Method %s from %s expects a callable as expectation.', $method, $method->mock()->mockery_getName(), ));
|
||||||
|
}
|
||||||
|
|
||||||
|
$method->andReturnUsing($expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxies calls to the original mock object.
|
||||||
|
*
|
||||||
|
* @param array<int, mixed> $arguments
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __call(string $method, array $arguments)
|
||||||
|
{
|
||||||
|
/* @phpstan-ignore-next-line */
|
||||||
|
return $this->mock->{$method}($arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -63,6 +63,7 @@
|
|||||||
✓ it allows to call underlying protected/private methods
|
✓ it allows to call underlying protected/private methods
|
||||||
✓ it throws error if method do not exist
|
✓ it throws error if method do not exist
|
||||||
✓ it can forward unexpected calls to any global function
|
✓ it can forward unexpected calls to any global function
|
||||||
|
✓ it can use helpers from helpers file
|
||||||
|
|
||||||
PASS Tests\Features\HigherOrderTests
|
PASS Tests\Features\HigherOrderTests
|
||||||
✓ it proxies calls to object
|
✓ it proxies calls to object
|
||||||
@ -77,7 +78,8 @@
|
|||||||
✓ it will throw exception from call if no macro exists
|
✓ it will throw exception from call if no macro exists
|
||||||
|
|
||||||
PASS Tests\Features\Mocks
|
PASS Tests\Features\Mocks
|
||||||
✓ it has bar
|
✓ it can mock methods
|
||||||
|
✓ it allows access to the underlying mockery mock
|
||||||
|
|
||||||
PASS Tests\Features\PendingHigherOrderTests
|
PASS Tests\Features\PendingHigherOrderTests
|
||||||
✓ get 'foo' → get 'bar' → expect true → toBeTrue
|
✓ get 'foo' → get 'bar' → expect true → toBeTrue
|
||||||
@ -221,5 +223,5 @@
|
|||||||
✓ it is a test
|
✓ it is a test
|
||||||
✓ it uses correct parent class
|
✓ it uses correct parent class
|
||||||
|
|
||||||
Tests: 7 skipped, 119 passed
|
Tests: 7 skipped, 121 passed
|
||||||
|
|
||||||
@ -42,3 +42,5 @@ it('throws error if method do not exist', function () {
|
|||||||
})->throws(\ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::name()');
|
})->throws(\ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::name()');
|
||||||
|
|
||||||
it('can forward unexpected calls to any global function')->_assertThat();
|
it('can forward unexpected calls to any global function')->_assertThat();
|
||||||
|
|
||||||
|
it('can use helpers from helpers file')->myAssertTrue(true);
|
||||||
|
|||||||
@ -1,17 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use function Tests\mock;
|
use Mockery\CompositeExpectation;
|
||||||
|
use Mockery\MockInterface;
|
||||||
|
|
||||||
interface Foo
|
interface Http
|
||||||
{
|
{
|
||||||
public function bar(): int;
|
public function get(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
it('has bar', function () {
|
it('can mock methods', function () {
|
||||||
$mock = mock(Foo::class);
|
$mock = mock(Http::class)->expect(
|
||||||
$mock->shouldReceive('bar')
|
get: fn () => 'foo',
|
||||||
->times(1)
|
);
|
||||||
->andReturn(2);
|
|
||||||
|
|
||||||
$mock->bar();
|
expect($mock->get())->toBe('foo');
|
||||||
});
|
})->skip(((float) phpversion()) < 8.0);
|
||||||
|
|
||||||
|
it('can access to arguments', function () {
|
||||||
|
$mock = mock(Http::class)->expect(
|
||||||
|
get: fn ($foo) => $foo,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($mock->get('foo'))->toBe('foo');
|
||||||
|
})->skip(((float) phpversion()) < 8.0);
|
||||||
|
|
||||||
|
it('allows access to the underlying mockery mock', function () {
|
||||||
|
$mock = mock(Http::class);
|
||||||
|
|
||||||
|
expect($mock->expect())->toBeInstanceOf(MockInterface::class);
|
||||||
|
expect($mock->shouldReceive())->toBeInstanceOf(CompositeExpectation::class);
|
||||||
|
})->skip(((float) phpversion()) < 8.0);
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests;
|
function myAssertTrue($value)
|
||||||
|
|
||||||
use Mockery;
|
|
||||||
use Mockery\MockInterface;
|
|
||||||
|
|
||||||
function mock(string $class): MockInterface
|
|
||||||
{
|
{
|
||||||
return Mockery::mock($class);
|
test()->assertTrue($value);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user