pestphp / pestphp/pest

`expect()->toBeInstanceOf()` / `not->toBeNull()` / `toBeString()` etc. don't narrow types like PHPUnit's assert methods`

Open
#1,888 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
11.7k
Forks
538
Avg merge
4d 11h
Merged PRs (30d)
8

Description

This issue has been made with Claude's help, but it's a real human asking 😊

Context

PHPUnit's assertInstanceOf(), assertNotNull(), assertIsString()/Int/Float/Array/Numeric carry @phpstan-assert annotations directly in phpunit/phpunit's Assert class (e.g. @phpstan-assert null $actual on assertNotNull). PHPStan's core reads these natively and narrows the variable's type in the Scope for every subsequent line.

The Pest equivalents (expect($x)->toBeInstanceOf(Y::class), expect($x)->not->toBeNull(), expect($x)->toBeString(), …) don't do this. I checked pest-plugin-phpstan's source and found RedundantExpectationRule / ImpossibleExpectationRule (backed by MatcherAssertionRegistry), which read the scope's already-known type to flag a redundant/impossible expectation — but no class in the package implements PHPStan's TypeSpecifyingExtension, so nothing ever writes a narrowed type back into scope.

Impact

In a large test suite (Laravel/Larastan, level 9), converting $this->assertInstanceOf(...) / $this->assertNotNull(...) to their Pest expect() equivalents breaks static analysis on every line afterward that reads the now-unnarrowed value (Cannot access property $x on Y|null, ... on mixed, etc.). We ended up having to keep ~130 PHPUnit-style assertions specifically for this reason, everywhere the asserted value is read again.

Question

Is this a deliberate scope limitation, or something that could be implemented via a TypeSpecifyingExtension for the matchers in MatcherAssertionRegistry::METHOD_ASSERTIONS (toBeString, toBeInt, toBeFloat, toBeArray, toBeInstanceOf, not->toBeNull, …)? Happy to help investigate/contribute if it's wanted — just want to check first whether there's a known reason (e.g. the ->not chain, or higher-order testing, making this unsafe to implement in general) before looking into a PR.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading MatcherAssertionRegistry::METHOD_ASSERTIONS and the existing RedundantExpectationRule and ImpossibleExpectationRule in pest-plugin-phpstan. Investigate how a PHPStan TypeSpecifyingExtension could consume the Pest matchers, including not-toBeNull and toBeInstanceOf. Done means supported expectations narrow the value for subsequent analysis without making unsafe assumptions, with the relevant static-analysis behavior verified.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
testing, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.