GitHub Actions中PHPUnit无法创建Mock文件问题求助
问题描述
本地环境下测试运行正常,但在GitHub Actions中执行测试时,无法创建Mock所需文件,导致测试失败,不清楚如何为GitHub Actions配置写入权限。
当前GitHub Action配置
name: PHP Composer on: push: branches: [ "main" ] pull_request: branches: [ "main" ] permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Validate composer.json and composer.lock run: composer validate --strict - name: Cache Composer packages id: composer-cache uses: actions/cache@v3 with: path: vendor key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-php- - name: Install dependencies run: composer install --prefer-dist --no-progress # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" # Docs: https://getcomposer.org/doc/articles/scripts.md - name: Run test suite run: ./vendor/bin/phpunit
测试失败结果
PHPUnit 11.5.4 by Sebastian Bergmann and contributors. Runtime: PHP 8.4.3 Configuration: /home/runner/work/archers-framework/archers-framework/phpunit.xml ..........FF..F..........................FF..FF.................. 65 / 97 ( 67%) ................................ 97 / 97 (100%) Time: 00:00.072, Memory: 10.00 MB Authority (Tests\ArchersFramework\Http\Authority) ✔ Valid authority string ✔ Authority string without credentials ✔ Authority string without port ✔ Authority string without credentials and port ✔ Invalid authority string ✔ Authority with credentials only ✔ Authority to string Container ✔ Get returns same instance for singleton ✔ Get returns new instance for factory ✔ Instance returns same object ✔ Autowires dependencies ✔ Throws exception for not autowireable class ✔ Inject attribute overrides default ✔ Throws exception for missing type hint ✔ Throws exception for unresolvable scalar type ✔ Handles nullable parameters ✔ Detects circular dependency Controller Discovery (ArchersFramework\Tests\Router\Discovery\ControllerDiscovery) ✘ Discover controllers in windows path │ │ Failed asserting that an array contains 'App\Controllers\WindowsTestController'. │ │ /home/runner/work/archers-framework/archers-framework/tests/Router/Discovery/ControllerDiscoveryTest.php:85 │ /home/runner/work/archers-framework/archers-framework/vendor/bin/phpunit:122 │ ✘ Discover controllers in linux path │ │ Failed asserting that an array contains 'App\Controllers\LinuxTestController'. │ │ /home/runner/work/archers-framework/archers-framework/tests/Router/Discovery/ControllerDiscoveryTest.php:98 │ /home/runner/work/archers-framework/archers-framework/vendor/bin/phpunit:122 │ ✔ Ignores non php files ✔ Ignores php files with wrong extension ✘ Handles nested directories │ │ Failed asserting that an array contains 'App\Controllers\Nested\DeepController'. │ │ /home/runner/work/archers-framework/archers-framework/tests/Router/Discovery/ControllerDiscoveryTest.php:137 │ /home/runner/work/archers-framework/archers-framework/vendor/bin/phpunit:122 │ ✘ Handles mixed directory separators │ │ Failed asserting that an array contains 'App\Controllers\Mixed\PathController'. │ │ /home/runner/work/archers-framework/archers-framework/tests/Router/Discovery/ControllerDiscoveryTest.php:150 │ /home/runner/work/archers-framework/archers-framework/vendor/bin/phpunit:122 │ ✔ Handles empty directory gracefully Glob Recursive (Tests\ArchersFramework\GlobRecursive) ✘ Glob recursive finds files │ │ Failed asserting that actual size 0 matches expected size 2. │ │ /home/runner/work/archers-framework/archers-framework/tests/GlobRecursiveTest.php:53 │ ✘ Glob recursive ignores non matching files │ │ Failed asserting that actual size 0 matches expected size 1. │ │ /home/runner/work/archers-framework/archers-framework/tests/GlobRecursiveTest.php:62 │ ✔ Glob recursive handles empty directory ✔ Glob recursive with no matches ✘ Glob recursive handles complex directory structures │ │ Failed asserting that actual size 0 matches expected size 3. │ │ /home/runner/work/archers-framework/archers-framework/tests/GlobRecursiveTest.php:89 │ Header Collection (Tests\ArchersFramework\Http\HeaderCollection) ✔ Constructor initializes headers ✔ Get returns null if header does not exist ✔ Add adds new header value ✔ Set replaces existing header values ✔ Delete removes header ✔ Delete does nothing if header does not exist ✔ Get all returns all headers ✔ Headers are case insensitive Path (ArchersFramework\Tests\Utilities\Path) ✔ Path joins multiple segments ✔ Path normalizes slashes ✔ Path trims leading and trailing slashes ✔ Empty path returns empty string ✔ Single segment path ✔ Path with mixed separators ✔ Windows absolute path ✔ Windows u n c path ✔ Linux absolute path ✔ Path with empty segments ✔ Path with multiple consecutive slashes ✔ Path with special characters ✔ Path with trailing backslash Response ✔ Response constructor ✔ Send response Route Discovery (ArchersFramework\Tests\Router\Discovery\RouteDiscovery) ✔ Discover routes from controller ✔ Discover routes with prefix from route collection ✔ Discover no routes ✔ Discover invalid controller class ✔ Discover routes with multiple controllers Router (ArchersFramework\Tests\Router\Router) ✔ Dispatch returns response for valid route ✔ Dispatch handles parameterized route ✔ Dispatch throws not found for invalid route ✔ Dispatch throws method not allowed ✔ Resolve parameters returns parameters ✔ Resolve parameters returns null for mismatch Template (ArchersFramework\Tests\Router\Template) ✔ Constructor parses valid template string ✔ Constructor defaults type to string ✔ Constructor throws exception for invalid template ✔ Is value valid returns true for valid int ✔ Is value valid returns false for invalid int ✔ Is value valid returns true for valid float ✔ Is value valid returns false for invalid float ✔ Is value valid returns true for boolean ✔ Is value valid returns false for invalid boolean ✔ Is value valid returns true for string ✔ Get processed value returns int ✔ Get processed value returns float ✔ Get processed value returns bool ✔ Get processed value returns string ✔ Get processed value throws exception for invalid value ULID (Tests\ArchersFramework\Utilities\ULID) ✔ Generated ulid has correct length ✔ Generated ulid has valid characters ✔ Generated ulids are unique ✔ Generated ulid is uppercase by default ✔ Generated ulid can be lowercase ✔ Generated ulid parts are correct ✔ To string method returns expected value ✔ From string with valid ulid ✔ From string with lowercase ulid ✔ From string with invalid ulid throws exception URI ✔ From string valid uri ✔ From string without query and fragment ✔ From string without schema ✔ From string without path ✔ From string invalid uri ✔ To string ✔ From string with port ✔ From string with credentials ✔ From string without credentials FAILURES! Tests: 97, Assertions: 166, Failures: 7. Error: Process completed with exit code 1.
解决方案
1. 澄清GitHub Actions权限配置
当前配置中的permissions: contents: read是针对GitHub仓库API的读写权限,不影响本地文件系统的写入。GitHub Actions runner的工作目录(/home/runner/work/<repo>/<repo>)和系统临时目录(/tmp)默认拥有写入权限,无需额外配置。
如果测试需要修改仓库内文件并提交(比如生成测试报告到仓库),可调整权限为:
permissions: contents: write
2. 排查测试代码中的文件路径问题
测试失败均为文件查找失败,大概率是Mock文件未正确生成,建议:
- 使用绝对路径创建临时文件:在PHP中可通过
sys_get_temp_dir()获取系统临时目录,确保路径在任何环境下都有效; - 添加调试输出:在测试失败的代码位置前加入文件存在性检查,比如:
$mockDir = sys_get_temp_dir() . '/test-controllers'; var_dump(file_exists($mockDir), scandir($mockDir));
通过GitHub Actions日志确认文件是否真的被创建;
- 强制文件写入磁盘:创建文件后执行刷新操作,避免缓存导致文件未实际生成:
$file = fopen($mockFilePath, 'w'); fwrite($file, $content); fflush($file); fclose($file);
内容的提问来源于stack exchange,提问作者Ugo Brocard
相关产品推荐
相关产品推荐

