diff --git a/src/Traits/MockTrait.php b/src/Traits/MockTrait.php index b1f5442..11bdabf 100644 --- a/src/Traits/MockTrait.php +++ b/src/Traits/MockTrait.php @@ -2,12 +2,13 @@ namespace RonasIT\Support\Traits; -use Exception; -use Illuminate\Support\Arr; use Closure; +use Illuminate\Support\Arr; use phpmock\phpunit\PHPMock; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Rule\InvokedCount; +use ReflectionFunction; +use ReflectionMethod; trait MockTrait { @@ -129,30 +130,64 @@ protected function assertArguments( string $class, string $function, int $callIndex, - bool $isClass = true + bool $isClass = true, ): void { + $reflection = ($isClass) + ? new ReflectionMethod($class, $function) + : new ReflectionFunction($function); + + $reflectionArgs = $reflection->getParameters(); + + $this->assertArgumentsCount($actual, $expected, $reflectionArgs, $function); + + $this->fillOptionalArguments($reflectionArgs, $actual, $expected, $isClass); + + $message = ($isClass) + ? "Class '{$class}'\nMethod: '{$function}'\nMethod call index: {$callIndex}" + : "Namespace '{$class}'\nFunction: '{$function}'\nCall index: {$callIndex}"; + + $this->compareArguments($actual, $expected, $message); + } + + protected function assertArgumentsCount(array $actual, array $expected, array $reflectionArgs, string $function): void + { $expectedCount = count($expected); $actualCount = count($actual); + $requiredParametersCount = count(array_filter($reflectionArgs, fn ($param) => !$param->isOptional())); if ($expectedCount !== $actualCount) { - $requiredParametersCount = count(array_filter($actual, fn ($item) => $item !== self::OPTIONAL_ARGUMENT_NAME)); + $this->assertFalse( + $expectedCount < $requiredParametersCount, + "Failed assert that function {$function} was called with {$expectedCount} arguments, actually it has {$requiredParametersCount} required arguments." + ); - if ($expectedCount > $actualCount || $expectedCount < $requiredParametersCount) { - throw new Exception("Failed assert that function {$function} was called with {$expectedCount} arguments, actually it calls with {$actualCount} arguments."); - } + $this->assertFalse( + $expectedCount > $actualCount, + "Failed assert that function {$function} was called with {$expectedCount} arguments, actually has {$actualCount} arguments." + ); } + } - $expected = array_pad($expected, $actualCount, self::OPTIONAL_ARGUMENT_NAME); + protected function fillOptionalArguments(array $parameters, array &$actual, array &$expected, bool $isClass): void + { + foreach ($parameters as $index => $parameter) { + if (!$isClass && $actual[$index] === self::OPTIONAL_ARGUMENT_NAME) { + $actual[$index] = $parameter->getDefaultValue(); + } - $message = ($isClass) - ? "Class '{$class}'\nMethod: '{$function}'\nMethod call index: {$callIndex}" - : "Namespace '{$class}'\nFunction: '{$function}'\nCall index: {$callIndex}"; + if (!isset($expected[$index]) && $parameter->isOptional()) { + $expected[$index] = $parameter->getDefaultValue(); + } + } + } + protected function compareArguments(array $actual, array $expected, string $message): void + { foreach ($actual as $index => $argument) { $this->assertEquals( $expected[$index], $argument, - "Failed asserting that arguments are equals to expected.\n{$message}\nArgument index: {$index}" + "Failed asserting that arguments are equal to expected.\n{$message}\nArgument index: {$index}" ); } } diff --git a/tests/MockTraitTest.php b/tests/MockTraitTest.php index 5d2a248..9fc4f0a 100644 --- a/tests/MockTraitTest.php +++ b/tests/MockTraitTest.php @@ -2,8 +2,9 @@ namespace RonasIT\Support\Tests; +use PHPUnit\Framework\ExpectationFailedException; +use RonasIT\Support\Tests\Support\Mock\TestMockClass; use RonasIT\Support\Traits\MockTrait; -use Exception; class MockTraitTest extends HelpersTestCase { @@ -46,26 +47,113 @@ public function testMockWithDifferentFunction() $this->functionCall('rand', [1, 5], 2), $this->functionCall('is_array', [123]), $this->functionCall('rand', [6, 10], 7), - $this->functionCall('uniqid', [], '0987654321'), + $this->functionCall('uniqid', ['prefix'], '0987654321'), $this->functionCall(name: 'uniqid', result: '0987654321'), - $this->functionCall('array_slice', [[1, 2, 3, 4, 5], 2, 2], [3, 4]), $this->functionCall('array_slice', [[1, 2, 3, 4, 5], 2], [3, 4, 5]), + $this->functionCall('array_slice', [[1, 2, 3, 4, 5], 2, 2, 'preserve_keys'], [3, 4]), ]); $this->assertEquals(2, rand(1, 5)); $this->assertTrue(is_array(123)); $this->assertEquals(7, rand(6, 10)); + $this->assertEquals('0987654321', uniqid('prefix')); $this->assertEquals('0987654321', uniqid()); - $this->assertEquals('0987654321', uniqid()); - $this->assertEquals([3, 4], array_slice([1, 2, 3, 4, 5], 2, 2)); $this->assertEquals([3, 4, 5], array_slice([1, 2, 3, 4, 5], 2)); + $this->assertEquals([3, 4], array_slice([1, 2, 3, 4, 5], 2, 2, 'preserve_keys')); + } + + public function testMockNativeFunctionWhenLessRequiredParameters() + { + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Failed assert that function array_slice was called with 1 arguments, actually it has 2 required arguments.'); + + $this->mockNativeFunction('RonasIT\Support\Tests', [ + $this->functionCall( + name: 'array_slice', + arguments: [[1, 2, 3, 4, 5]], + result: [3, 4], + ), + ]); + + array_slice([1, 2, 3, 4, 5], 2, 2); + } + + public function testMockNativeFunctionWhenMoreExpectedParameters() + { + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Failed assert that function array_slice was called with 5 arguments, actually has 4 arguments.'); + + $this->mockNativeFunction('RonasIT\Support\Tests', [ + $this->functionCall( + name: 'array_slice', + arguments: [[1, 2, 3, 4, 5], 2, 2, 'preserve_keys', 'extra_parameter'], + result: [3, 4], + ), + ]); + + array_slice([1, 2, 3, 4, 5], 2, 2, 'preserve_keys'); + } + + public function testMockNativeFunctionCheckMockedResult() + { + $this->mockNativeFunction('RonasIT\Support\Tests', [ + $this->functionCall( + name: 'array_slice', + arguments: [[1, 2, 3, 4, 5], 2, 2], + result: [3, 4], + ), + ]); + + $this->assertEquals([3, 4], array_slice([1, 2, 3, 4, 5], 2, 2)); + } + + public function testMockFunctionInClass() + { + $mock = $this->mockClass(TestMockClass::class, [ + $this->functionCall('mockFunction', ['firstRequired', 'secondRequired'], 'mockFunction'), + $this->functionCall('mockFunction', ['firstRequired', 'secondRequired', 'firstOptional'], 'mockFunction'), + $this->functionCall('mockFunction', ['firstRequired', 'secondRequired', 'firstOptional', 'secondOptional'], 'mockFunction'), + ]); + + $this->assertEquals('mockFunction', $mock->mockFunction('firstRequired', 'secondRequired')); + $this->assertEquals('mockFunction', $mock->mockFunction('firstRequired', 'secondRequired', 'firstOptional')); + $this->assertEquals('mockFunction', $mock->mockFunction('firstRequired', 'secondRequired', 'firstOptional', 'secondOptional')); + } + + public function testMockClassMethodCheckMockedResult() + { + $mock = $this->mockClass(TestMockClass::class, [ + $this->functionCall('mockFunction', ['firstRequired', 'secondRequired'], 'mocked_result'), + ]); + + $this->assertEquals('mocked_result', $mock->mockFunction('firstRequired', 'secondRequired')); + } + + public function testMockClassMethodWhenLessRequiredParameters() + { + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Failed assert that function mockFunction was called with 1 arguments, actually it has 2 required arguments.'); + + $this->assertArguments( + actual: ['firstRequired', 'secondRequired', 'string', null], + expected: ['firstRequired'], + class: TestMockClass::class, + function: 'mockFunction', + callIndex: 0, + ); } - public function testAssertArgumentMismatchBetweenExpectedAndActualArguments() + public function testMockClassMethodWhenMoreExpectedParameters() { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Failed assert that function testFunction was called with 2 arguments, actually it calls with 1 arguments.'); + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Failed assert that function mockFunction was called with 5 arguments, actually has 4 arguments.'); - $this->assertArguments(['test'], ['test', ''], 'TestClass', 'testFunction', 0); + $this->assertArguments( + actual: ['firstRequired', 'secondRequired', 'string', null], + expected: ['firstRequired', 'secondRequired', 'firstOptional', 'secondOptional', 'thirdOptional'], + class: TestMockClass::class, + function: 'mockFunction', + callIndex: 0, + ); } } diff --git a/tests/support/Mock/TestMockClass.php b/tests/support/Mock/TestMockClass.php new file mode 100644 index 0000000..75a5235 --- /dev/null +++ b/tests/support/Mock/TestMockClass.php @@ -0,0 +1,15 @@ +