Feature: TestCase In order to have typed TestCases As a Psalm user I need Psalm to typecheck my test cases Background: Given I have the following config """ """ And I have the following code preamble """ expectException(MyTestCase::class); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidArgument | Argument 1 of PHPUnit\Framework\TestCase::expectException expects class-string, NS\MyTestCase::class provided | And I see no other errors Scenario: TestCase::expectException() accepts throwables Given I have the following code """ class MyTestCase extends TestCase { /** @return void */ public function testSomething() { $this->expectException(\InvalidArgumentException::class); } } """ When I run Psalm Then I see no errors Scenario: Stateful test case with setUp produces no MissingConstructor Given I have the following code """ use Prophecy\Prophecy\ObjectProphecy; interface I { public function work(): int; } class MyTestCase extends TestCase { /** @var ObjectProphecy */ private $i; /** @return void */ public function setUp() { $this->i = $this->prophesize(I::class); } /** @return void */ public function testSomething() { $this->i->work()->willReturn(1);; $i = $this->i->reveal(); $this->assertEquals(1, $i->work()); } } """ When I run Psalm Then I see no errors Scenario: Stateful test case with @before produces no MissingConstructor Given I have the following code """ use Prophecy\Prophecy\ObjectProphecy; interface I { public function work(): int; } class MyTestCase extends TestCase { /** @var ObjectProphecy */ private $i; /** * @before * @return void */ public function myInit() { $this->i = $this->prophesize(I::class); } /** @return void */ public function testSomething() { $this->i->work()->willReturn(1);; $i = $this->i->reveal(); $this->assertEquals(1, $i->work()); } } """ When I run Psalm Then I see no errors Scenario: Stateful test case without @before or setUp produces MissingConstructor Given I have the following code """ use Prophecy\Prophecy\ObjectProphecy; interface I { public function work(): int; } class MyTestCase extends TestCase { /** @var ObjectProphecy */ private $i; /** @return void */ public function myInit() { $this->i = $this->prophesize(I::class); } /** @return void */ public function testSomething() { $this->i->work()->willReturn(1);; $i = $this->i->reveal(); $this->assertEquals(1, $i->work()); } } """ When I run Psalm Then I see these errors | Type | Message | | MissingConstructor | NS\MyTestCase has an uninitialized variable $this->i, but no constructor | And I see no other errors Scenario: Missing data provider is reported Given I have the following code """ class MyTestCase extends TestCase { /** * @param mixed $int * @return void * @psalm-suppress UnusedMethod * @dataProvider provide */ public function testSomething($int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | UndefinedMethod | Provider method NS\MyTestCase::provide is not defined | And I see no other errors Scenario: Invalid iterable data provider is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield 1; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, iterable provided | And I see no other errors Scenario: Valid iterable data provider is allowed Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable> */ public function provide() { yield [1]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see no errors Scenario: Invalid generator data provider is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return \Generator */ public function provide() { yield 1; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, Generator provided | And I see no other errors Scenario: Valid generator data provider is allowed Given I have the following code """ class MyTestCase extends TestCase { /** @return \Generator,mixed,void> */ public function provide() { yield [1]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see no errors Scenario: Invalid array data provider is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return array */ public function provide() { return [1 => 1]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, array provided | And I see no other errors Scenario: Underspecified array data provider is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return array */ public function provide() { return [1 => [1]]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, possibly different array provided | And I see no other errors Scenario: Underspecified iterable data provider is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { return [1 => [1]]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, possibly different iterable provided | And I see no other errors Scenario: Underspecified generator data provider is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return \Generator */ public function provide() { yield 1 => [1]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, possibly different Generator provided | And I see no other errors Scenario: Valid array data provider is allowed Given I have the following code """ class MyTestCase extends TestCase { /** @return array> */ public function provide() { return [ "data set name" => [1], ]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see no errors Scenario: Valid object data provider is allowed Given I have the following code """ class MyTestCase extends TestCase { /** @return \ArrayObject> */ public function provide() { return new \ArrayObject([ "data set name" => [1], ]); } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see no errors Scenario: Invalid dataset shape is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield "data set name" => ["str"]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidArgument | Argument 1 of NS\MyTestCase::testSomething expects int, string provided by NS\MyTestCase::provide():(iterable) | And I see no other errors Scenario: Invalid dataset array is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable> */ public function provide() { yield "data set name" => ["str"]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | PossiblyInvalidArgument | Argument 1 of NS\MyTestCase::testSomething expects int, string\|int provided by NS\MyTestCase::provide():(iterable>) | And I see no other errors Scenario: Shape dataset with missing params is reported Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield "data set name" => [1]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int, int $i) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | TooFewArguments | Too few arguments for NS\MyTestCase::testSomething - expecting 2 but saw 1 provided by NS\MyTestCase::provide():(iterable) | And I see no other errors Scenario: Referenced providers are not marked as unused Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield "data set name" => [1]; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm with dead code detection Then I see no errors Scenario: Unreferenced providers are marked as unused Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield "data set name" => [1]; } /** * @return void */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm with dead code detection Then I see these errors | Type | Message | | PossiblyUnusedMethod | Cannot find public calls to method NS\MyTestCase::provide | And I see no other errors Scenario: Test method are never marked as unused Given I have the following code """ class MyTestCase extends TestCase { /** * @return void */ public function testSomething(int $int) { $this->assertEquals(1, $int); } /** * @return void * @test */ public function somethingElse(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm with dead code detection Then I see no errors Scenario: Unreferenced non-test methods are marked as unused Given I have the following code """ class MyTestCase extends TestCase { /** * @return void */ public function somethingElse(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm with dead code detection Then I see these errors | Type | Message | | PossiblyUnusedMethod | Cannot find public calls to method NS\MyTestCase::somethingElse | And I see no other errors Scenario: Unreferenced TestCase descendants are never marked as unused Given I have the following code """ class MyTestCase extends TestCase { } """ When I run Psalm with dead code detection Then I see no errors Scenario: Unreferenced non-test classes are marked as unused Given I have the following code """ class UtilityClass { } """ When I run Psalm with dead code detection Then I see these errors | Type | Message | | UnusedClass | Class NS\UtilityClass is never used | And I see no other errors Scenario: Provider returning possibly undefined offset is fine when test method has default for that param Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield "data set name" => rand(0,1) ? [1] : []; } /** * @return void * @dataProvider provide */ public function testSomething(int $int = 2) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see no errors Scenario: Provider returning possibly undefined offset with mismatching type is reported even when test method has default for that param Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield "data set name" => rand(0,1) ? ["1"] : []; } /** * @return void * @dataProvider provide */ public function testSomething(int $int = 2) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidArgument | Argument 1 of NS\MyTestCase::testSomething expects int, string provided by NS\MyTestCase::provide():(iterable) | And I see no other errors Scenario: Provider returning possibly undefined offset is marked when test method has no default for that param Given I have the following code """ class MyTestCase extends TestCase { /** @return iterable */ public function provide() { yield "data set name" => rand(0,1) ? [1] : []; } /** * @return void * @dataProvider provide */ public function testSomething(int $int) { $this->assertEquals(1, $int); } } """ When I run Psalm Then I see these errors | Type | Message | | InvalidArgument | Argument 1 of NS\MyTestCase::testSomething has no default value, but possibly undefined int provided by NS\MyTestCase::provide():(iterable) | And I see no other errors