You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 16:54:53