`expect()->toBeInstanceOf()` / `not->toBeNull()` / `toBeString()` 等不像 PHPUnit 的 assert 方法那样限制类型
这个问题是借助 Claude 的帮助解决的,但这是一个真正的人在询问 背景
PHPUnit 的 assertInstanceOf(), assertNotNull(), assertIsString()/Int/Float/Array/Numeric 在 phpunit/phpunit 的 Assert 类中直接带有 @phpstan-assert 注解(例如,在 assertNotNull() 上的 @phpstan-assert null $actual)。PHPStan 的核心会直接读取这些内容,并在每一行后为变量的类型在 Scope 中进行窄化。Pest 的等价物(expect($x)->toBeInstanceOf(Y::class), expect($x)->not->toBeNull(), expect($x)->toBeString(), …) 并没有这样做。我检查了 pest-plugin-phpstan 的源代码,发现了 RedundantExpectationRule / ImpossibleExpectationRule(由 MatcherAssertionRegistry 提供支持),这些规则会读取范围中已知的类型,以标记一个冗余/不可能的预期值 - 但该包中的任何类都没有实现 PHPStan 的 TypeSpecifyingExtension,因此从来没有将窄化的类型写回范围中。 影响
在一个大型测试套件(例如 Laravel/Larastan, 级别 9)中,将 $this->assertInstanceOf(...) / $this->assertNotNull(...) 转换为它们的 Pest expect() 等价物会导致后续每一行的静态分析出现问题,这些行会读取现在已不再窄化的值(例如, "无法访问属性 $x 在 Y/null 上", "… 在混合类型上", 等等)。我们最终不得不在每个被断言的值再次读取的地方保留约 130 个 PHPUnit 样式的断言,以此作为理由。 问题
这是否是一个故意的范围限制,还是可以通过 TypeSpecifyingExtension 为 MatcherAssertionRegistry::METHOD_ASSERTIONS 中的匹配器(如 toBeString, toBeInt, toBeFloat, toBeArray, toBeInstanceOf, not->toBeNull, …) 来实现?如果需要,我很乐意帮助调查/贡献 - 只是想先确认是否存在已知的原因(例如, ->not 链,或高阶测试,使这种实现在一般情况下是不安全的),然后再寻找 PR。
内容来源: pestphp/pest