Skip to content

Commit

Permalink
Warn of non-existent methods in createPartialMock
Browse files Browse the repository at this point in the history
  • Loading branch information
DFoxinator authored and sebastianbergmann committed Jul 31, 2019
1 parent 5e04f91 commit 20b0b21
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 9 deletions.
3 changes: 2 additions & 1 deletion .psalm/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@
</TypeDoesNotContainNull>
</file>
<file src="src/Framework/TestCase.php">
<ArgumentTypeCoercion occurrences="1">
<ArgumentTypeCoercion occurrences="2">
<code>$class_name</code>
<code>$this-&gt;expectedException</code>
</ArgumentTypeCoercion>
<InvalidArgument occurrences="2">
Expand Down
16 changes: 14 additions & 2 deletions src/Framework/MockObject/MockBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ public function setMethods(array $methods = null): self
{
$this->methods = $methods;

$this->alreadyUsedMockMethodConfiguration = true;

return $this;
}

Expand All @@ -206,7 +208,12 @@ public function setMethods(array $methods = null): self
public function onlyMethods(array $methods): self
{
if ($this->alreadyUsedMockMethodConfiguration) {
throw new RuntimeException('Can\'t use onlyMethods after already configuring mock methods on the mock.');
throw new RuntimeException(
\sprintf(
'Can\'t use onlyMethods on "%s" mock because mocked methods were already configured.',
$this->type
)
);
}

$this->alreadyUsedMockMethodConfiguration = true;
Expand Down Expand Up @@ -240,7 +247,12 @@ public function onlyMethods(array $methods): self
public function addMethods(array $methods): self
{
if ($this->alreadyUsedMockMethodConfiguration) {
throw new RuntimeException('Can\'t use addMethods after already configuring mock methods on the mock.');
throw new RuntimeException(
\sprintf(
'Can\'t use addMethods on "%s" mock because mocked methods were already configured.',
$this->type
)
);
}

$this->alreadyUsedMockMethodConfiguration = true;
Expand Down
20 changes: 20 additions & 0 deletions src/Framework/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,26 @@ protected function createConfiguredMock($originalClassName, array $configuration
*/
protected function createPartialMock($originalClassName, array $methods): MockObject
{
$class_names = \is_array($originalClassName) ? $originalClassName : [$originalClassName];

foreach ($class_names as $class_name) {
$reflection = new \ReflectionClass($class_name);

$mockedMethodsThatDontExist = \array_filter($methods, function (string $method) use ($reflection) {
return !$reflection->hasMethod($method);
});

if ($mockedMethodsThatDontExist) {
$this->addWarning(
\sprintf(
'createPartialMock called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.',
\implode(', ', $mockedMethodsThatDontExist),
$class_name
)
);
}
}

return $this->getMockBuilder($originalClassName)
->disableOriginalConstructor()
->disableOriginalClone()
Expand Down
11 changes: 11 additions & 0 deletions tests/_files/TestWithDifferentStatuses.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,15 @@ public function testThatAddsAWarning(): void
{
$this->addWarning('Sorry, Dave!');
}

public function testWithCreatePartialMockWarning(): void
{
$this->createPartialMock(\Mockable::class, ['mockableMethod', 'fakeMethod1', 'fakeMethod2']);
}

public function testWithCreatePartialMockPassesNoWarning(): void
{
$mock = $this->createPartialMock(\Mockable::class, ['mockableMethod']);
$this->assertNull($mock->mockableMethod());
}
}
52 changes: 46 additions & 6 deletions tests/unit/Framework/MockObject/MockBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public function testMethodExceptionsToMockCanBeSpecified(): void
public function testSetMethodsAllowsNonExistentMethodNames(): void
{
$mock = $this->getMockBuilder(Mockable::class)
->setMethods(['mockableMethodWithCrazyName'])
->getMock();
->setMethods(['mockableMethodWithCrazyName'])
->getMock();

$this->assertNull($mock->mockableMethodWithCrazyName());
}
Expand All @@ -65,8 +65,8 @@ public function testOnlyMethodsWithNonExistentMethodNames(): void
$this->expectException(RuntimeException::class);

$this->getMockBuilder(Mockable::class)
->onlyMethods(['mockableMethodWithCrazyName'])
->getMock();
->onlyMethods(['mockableMethodWithCrazyName'])
->getMock();
}

public function testOnlyMethodsWithExistingMethodNames(): void
Expand Down Expand Up @@ -101,8 +101,8 @@ public function testAddMethodsWithExistingMethodNames(): void
public function testEmptyMethodExceptionsToMockCanBeSpecified(): void
{
$mock = $this->getMockBuilder(Mockable::class)
->setMethodsExcept()
->getMock();
->setMethodsExcept()
->getMock();

$this->assertNull($mock->mockableMethod());
$this->assertNull($mock->anotherMockableMethod());
Expand All @@ -128,6 +128,46 @@ public function testNotAbleToUseOnlyMethodsAfterAddMethods(): void
->getMock();
}

public function testAbleToUseSetMethodsAfterOnlyMethods(): void
{
$mock = $this->getMockBuilder(Mockable::class)
->onlyMethods(['mockableMethod'])
->setMethods(['mockableMethodWithCrazyName'])
->getMock();

$this->assertNull($mock->mockableMethodWithCrazyName());
}

public function testAbleToUseSetMethodsAfterAddMethods(): void
{
$mock = $this->getMockBuilder(Mockable::class)
->addMethods(['notAMethod'])
->setMethods(['mockableMethodWithCrazyName'])
->getMock();

$this->assertNull($mock->mockableMethodWithCrazyName());
}

public function testNotAbleToUseAddMethodsAfterSetMethods(): void
{
$this->expectException(RuntimeException::class);

$this->getMockBuilder(Mockable::class)
->setMethods(['mockableMethod'])
->addMethods(['mockableMethodWithFakeMethod'])
->getMock();
}

public function testNotAbleToUseOnlyMethodsAfterSetMethods(): void
{
$this->expectException(RuntimeException::class);

$this->getMockBuilder(Mockable::class)
->setMethods(['mockableMethodWithFakeMethod'])
->onlyMethods(['mockableMethod'])
->getMock();
}

public function testByDefaultDoesNotPassArgumentsToTheConstructor(): void
{
$mock = $this->getMockBuilder(Mockable::class)->getMock();
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/Framework/TestCaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,26 @@ public function testCreatePartialMockCanMockNoMethods(): void
$this->assertTrue($mock->anotherMockableMethod());
}

public function testCreatePartialMockWithFakeMethods(): void
{
$test = new \TestWithDifferentStatuses('testWithCreatePartialMockWarning');

$test->run();

$this->assertSame(BaseTestRunner::STATUS_WARNING, $test->getStatus());
$this->assertFalse($test->hasFailed());
}

public function testCreatePartialMockWithRealMethods(): void
{
$test = new \TestWithDifferentStatuses('testWithCreatePartialMockPassesNoWarning');

$test->run();

$this->assertSame(BaseTestRunner::STATUS_PASSED, $test->getStatus());
$this->assertFalse($test->hasFailed());
}

public function testCreateMockSkipsConstructor(): void
{
/** @var \Mockable $mock */
Expand Down

0 comments on commit 20b0b21

Please sign in to comment.