Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#145: Fix bug with empty arguments #170

Merged
merged 9 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 46 additions & 12 deletions src/Traits/MockTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -129,30 +130,63 @@ protected function assertArguments(
string $class,
string $function,
int $callIndex,
bool $isClass = true
bool $isClass = true,
): void {
$reflection = $isClass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$reflection = $isClass
$reflection = ($isClass)

? new ReflectionMethod($class, $function)
: new ReflectionFunction($function);

$expectedCount = count($expected);
$actualCount = count($actual);
$parameters = $reflection->getParameters();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$parameters = $reflection->getParameters();
$reflectionArgs = $reflection->getParameters();

$requiredParametersCount = count(array_filter($parameters, fn ($param) => !$param->isOptional()));

$this->assertArgumentCount($expectedCount, $actualCount, $requiredParametersCount, $function);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move counting into the assertArgumentCount function

Suggested change
$requiredParametersCount = count(array_filter($parameters, fn ($param) => !$param->isOptional()));
$this->assertArgumentCount($expectedCount, $actualCount, $requiredParametersCount, $function);
$this->assertArgumentCount($expected, $actual, $reflectionArgs, $function);


$this->fillOptionalArguments($parameters, $actual, $expected, $isClass);

$message = $isClass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$message = $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 assertArgumentCount(int $expectedCount, int $actualCount, int $requiredParametersCount, string $function): void
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
protected function assertArgumentCount(int $expectedCount, int $actualCount, int $requiredParametersCount, string $function): void
protected function assertArgumentsCount(int $expectedCount, int $actualCount, int $requiredParametersCount, string $function): void

{
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} require arguments, actually it calls with {$requiredParametersCount} require arguments."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Failed assert that function {$function} was called with {$expectedCount} require arguments, actually it calls with {$requiredParametersCount} require arguments."
"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 it calls with {$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 (!isset($expected[$index]) && $parameter->isOptional()) {
$expected[$index] = $parameter->getDefaultValue();
}

$message = ($isClass)
? "Class '{$class}'\nMethod: '{$function}'\nMethod call index: {$callIndex}"
: "Namespace '{$class}'\nFunction: '{$function}'\nCall index: {$callIndex}";
if (!$isClass && $actual[$index] === 'optionalParameter') {
$actual[$index] = $expected[$index] ?? $parameter->getDefaultValue();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move actual args filling before the expected args filling

Suggested change
$actual[$index] = $expected[$index] ?? $parameter->getDefaultValue();
$actual[$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}"
);
}
}
Expand Down
92 changes: 84 additions & 8 deletions tests/MockTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -46,26 +47,101 @@ 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());
$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));
}

public function testMockNativeFunctionWhenLessRequiredParameters()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('Failed assert that function array_slice was called with 1 require arguments, actually it calls with 2 require arguments.');

$this->mockNativeFunction('RonasIT\Support\Tests', [
$this->functionCall('array_slice', [[1, 2, 3, 4, 5]], [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 it calls with 4 arguments.');

$this->mockNativeFunction('RonasIT\Support\Tests', [
$this->functionCall('array_slice', [[1, 2, 3, 4, 5], 2, 2, 'preserve_keys', 'extra_parameter'], [3, 4]),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use multiline call style with named args

]);

array_slice([1, 2, 3, 4, 5], 2, 2, 'preserve_keys');
}

public function testMockNativeFunctionWhenDifferentResult()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function testMockNativeFunctionWhenDifferentResult()
public function testMockNativeFunctionCheckMockedResult()

{
$this->mockNativeFunction('RonasIT\Support\Tests', [
$this->functionCall('array_slice', [[1, 2, 3, 4, 5], 2, 2], [3, 4]),
]);

$this->assertNotEquals([3, 4, 5], array_slice([1, 2, 3, 4, 5], 2, 2));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->assertNotEquals([3, 4, 5], array_slice([1, 2, 3, 4, 5], 2, 2));
$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')));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->assertEquals('mockFunction', ($mock->mockFunction('firstRequired', 'secondRequired')));
$this->assertEquals('mockFunction', ($mock->mockFunction('firstRequired', 'secondRequired', 'firstOptional')));
$this->assertEquals('mockFunction', ($mock->mockFunction('firstRequired', 'secondRequired', 'firstOptional', 'secondOptional')));
$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 testMockFunctionInClassWhenDifferentResult()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function testMockFunctionInClassWhenDifferentResult()
public function testMockClassMethodCheckMockedResult()

{
$mock = $this->mockClass(TestMockClass::class, [
$this->functionCall('mockFunction', ['firstRequired', 'secondRequired'], 'mockFunction'),
]);

$this->assertNotEquals('result', $mock->mockFunction('firstRequired', 'secondRequired'));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->functionCall('mockFunction', ['firstRequired', 'secondRequired'], 'mockFunction'),
]);
$this->assertNotEquals('result', $mock->mockFunction('firstRequired', 'secondRequired'));
$this->functionCall('mockFunction', ['firstRequired', 'secondRequired'], 'mocked_result'),
]);
$this->assertEquals('mocked_result', $mock->mockFunction('firstRequired', 'secondRequired'));

}

public function testMockFunctionInClassWhenLessRequiredParameters()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function testMockFunctionInClassWhenLessRequiredParameters()
public function testMockClassMethodWhenLessRequiredParameters()

{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('Failed assert that function mockFunction was called with 1 require arguments, actually it calls with 2 require arguments.');

$this->assertArguments(
actual: ['firstRequired', 'secondRequired', 'string', null],
expected: ['firstRequired'],
class: TestMockClass::class,
function: 'mockFunction',
callIndex: 0,
);
}

public function testAssertArgumentMismatchBetweenExpectedAndActualArguments()
public function testMockFunctionInClassWhenMoreExpectedParameters()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function testMockFunctionInClassWhenMoreExpectedParameters()
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 it calls with 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,
);
}
}
15 changes: 15 additions & 0 deletions tests/support/Mock/TestMockClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace RonasIT\Support\Tests\Support\Mock;

class TestMockClass
{
public function mockFunction(
string $firsrRequeredParam,
string $secondRequiredParam,
string $firstOptionalParam = 'string',
?string $secondOptionalParam = null,
): string {
return 'mockFunction';
}
}
Loading