From f3b6b02767f293fb351d42349bc89477e66112e4 Mon Sep 17 00:00:00 2001 From: David Fox Date: Wed, 15 May 2019 11:30:54 -0400 Subject: [PATCH] Warn of non-existent methods in createPartialMock --- .psalm/baseline.xml | 6 ++- src/Framework/MockObject/MockBuilder.php | 2 + src/Framework/TestCase.php | 16 ++++++++ tests/_files/TestWithDifferentStatuses.php | 11 +++++ .../Framework/MockObject/MockBuilderTest.php | 40 +++++++++++++++++++ tests/unit/Framework/TestCaseTest.php | 20 ++++++++++ 6 files changed, 94 insertions(+), 1 deletion(-) diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml index 9f63d36fb39..8093b9697ae 100644 --- a/.psalm/baseline.xml +++ b/.psalm/baseline.xml @@ -223,11 +223,15 @@ $this->expectedException - + 0 $header + $originalClassName + + $originalClassName + null $beStrictAboutChangesToGlobalState diff --git a/src/Framework/MockObject/MockBuilder.php b/src/Framework/MockObject/MockBuilder.php index ff09628e5ff..e17114b572b 100644 --- a/src/Framework/MockObject/MockBuilder.php +++ b/src/Framework/MockObject/MockBuilder.php @@ -182,6 +182,8 @@ public function setMethods(array $methods = null): self { $this->methods = $methods; + $this->alreadyUsedMockMethodConfiguration = true; + return $this; } diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index 7c554249f82..ccdcbaedaf2 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -1388,6 +1388,22 @@ protected function createConfiguredMock($originalClassName, array $configuration */ protected function createPartialMock($originalClassName, array $methods): MockObject { + $reflection = new \ReflectionClass($originalClassName); + + $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), + $originalClassName + ) + ); + } + try { return $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() diff --git a/tests/_files/TestWithDifferentStatuses.php b/tests/_files/TestWithDifferentStatuses.php index e680f6ce2d3..da170332349 100644 --- a/tests/_files/TestWithDifferentStatuses.php +++ b/tests/_files/TestWithDifferentStatuses.php @@ -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()); + } } diff --git a/tests/unit/Framework/MockObject/MockBuilderTest.php b/tests/unit/Framework/MockObject/MockBuilderTest.php index 7c31c86d49a..158920428c9 100644 --- a/tests/unit/Framework/MockObject/MockBuilderTest.php +++ b/tests/unit/Framework/MockObject/MockBuilderTest.php @@ -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(); diff --git a/tests/unit/Framework/TestCaseTest.php b/tests/unit/Framework/TestCaseTest.php index 738f60ce082..d410cd8d5ad 100644 --- a/tests/unit/Framework/TestCaseTest.php +++ b/tests/unit/Framework/TestCaseTest.php @@ -856,6 +856,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 */