本地Rails测试全通过,Github Action中关联删除测试失败求助
问题情况
配置了Github Action,推送至main分支时自动运行Rails测试。本地执行rails test命令,74个测试全部通过;但在Github Action的工作流中,有2个涉及关联对象(带外键)删除的测试失败,报错为PostgreSQL外键约束冲突,提示Review、HalalItem对象删除时仍被Restaurant表引用。
本地测试输出
Running 74 tests in parallel using 2 processes DEBUGGER: Attaching after process 42736 fork to child process 42753 DEBUGGER[bin/rails#42754]: Attaching after process 42736 fork to child process 42754 Started with run options --seed 10960 74/74: [=================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 5.09323s 74 tests, 135 assertions, 0 failures, 0 errors, 0 skips
Github Action测试输出
Running 74 tests in parallel using 2 processes DEBUGGER: Attaching after process 3336 fork to child process 3357 DEBUGGER[bin/rails#3359]: Attaching after process 3336 fork to child process 3359 Started with run options --seed 27863 Progress: |=========ERROR ReviewsControllerTest#test_should_destroy_review (3.88s) Minitest::UnexpectedError: DRb::DRbRemoteError: PG::ForeignKeyViolation: ERROR: update or delete on table "reviews" violates foreign key constraint "fk_rails_7314ea0830" on table "restaurants" DETAIL: Key (id)=(1) is still referenced from table "restaurants". (ActiveRecord::InvalidForeignKey) app/controllers/reviews_controller.rb:52:in `destroy' test/controllers/reviews_controller_test.rb:43:in `block (2 levels) in <class:ReviewsControllerTest>' test/controllers/reviews_controller_test.rb:42:in `block in <class:ReviewsControllerTest>' =========ERROR HalalItemsControllerTest#test_should_destroy_halal_item (4.09s) Minitest::UnexpectedError: DRb::DRbRemoteError: PG::ForeignKeyViolation: ERROR: update or delete on table "halal_items" violates foreign key constraint "fk_rails_0fa9986d41" on table "restaurants" DETAIL: Key (id)=(1) is still referenced from table "restaurants". (ActiveRecord::InvalidForeignKey) app/controllers/halal_items_controller.rb:52:in `destroy' test/controllers/halal_items_controller_test.rb:43:in `block (2 levels) in <class:HalalItemsControllerTest>' test/controllers/halal_items_controller_test.rb:42:in `block in <class:HalalItemsControllerTest>' ==================================================| Finished in 5.04433s 74 tests, 131 assertions, 0 failures, 2 errors, 0 skips
Github Action工作流配置(main.yml)
name: Ruby on: push: branches: - main jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:12 ports: ['5432:5432'] env: POSTGRES_DB: **redacted** POSTGRES_PASSWORD: **redacted** POSTGRES_USER: **redacted** steps: - name: Checkout repo uses: actions/checkout@main - name: Setup Ruby uses: ruby/setup-ruby@v1 - name: Install dependencies run: | sudo apt-get -yqq install libpq-dev gem install bundler - name: Install gems run: | bundle install --jobs 4 --retry 3 - name: Setup database env: DATABASE_URL: **redacted** RAILS_ENV: test DISABLE_SPRING: 1 run: bundle exec rails db:prepare - name: Run tests env: DATABASE_URL: **redacted** RAILS_ENV: test DISABLE_SPRING: 1 run: bundle exec rails test
相关模型代码
app/models/review.rb
class Review < ApplicationRecord belongs_to :restaurant belongs_to :user belongs_to :halal_item end
app/models/halal_items.rb
class HalalItem < ApplicationRecord belongs_to :restaurant has_many :reviews, dependent: :nullify end
app/models/restaurants.rb
class Restaurant < ApplicationRecord has_many :reviews, dependent: :nullify has_many :halal_items, dependent: :nullify end
失败的测试及控制器代码
HalalItem删除测试与控制器
test/controllers/halal_items_controller_test.rb
test "should destroy halal_item" do assert_difference("HalalItem.count", -1) do delete halal_item_url(@halal_item) end assert_redirected_to halal_items_url end
app/controllers/halal_items_controller.rb
def destroy @halal_item.destroy # 测试失败的代码行 respond_to do |format| format.html { redirect_to halal_items_url, notice: "Halal item was successfully destroyed." } format.json { head :no_content } end end
Review删除测试与控制器
test/controllers/reviews_controller_test.rb
test "should destroy review" do assert_difference("Review.count", -1) do delete review_url(@review) end assert_redirected_to reviews_url end
app/controllers/reviews_controller.rb
def destroy @review.destroy # 测试失败的代码行 respond_to do |format| format.html { redirect_to reviews_url, notice: "Review was successfully destroyed." } format.json { head :no_content } end end
问题分析与解决步骤
核心问题排查
模型文件名不符合Rails约定:
Rails要求模型文件名为单数小写,对应类名单数大写。当前halal_items.rb(复数)、restaurants.rb(复数)不符合约定,会导致Rails自动加载模型失败,关联逻辑无法生效。本地可能因Spring缓存或开发环境自动修复机制正常运行,但Github Action是全新环境,模型加载异常直接触发数据库外键约束错误。数据库外键约束方向可能错误:
报错提示Restaurant表引用了Review/HalalItem的ID,说明数据库中可能存在反向外键(比如restaurants表有review_id或halal_item_id字段并设为外键),这和模型关联逻辑完全相反。正常逻辑应该是Review/HalalItem表通过restaurant_id外键关联Restaurant表。测试环境外键约束配置差异:
本地若使用SQLite,默认可能未开启外键约束,导致测试不触发错误;而Github Action用PostgreSQL,外键约束严格生效。
解决步骤
修正模型文件名:
- 将
app/models/halal_items.rb重命名为app/models/halal_item.rb - 将
app/models/restaurants.rb重命名为app/models/restaurant.rb
- 将
检查并修复数据库迁移:
- 查看所有迁移文件,确保外键约束方向正确:
- 创建reviews表的迁移需包含
t.references :restaurant, foreign_key: true(或add_foreign_key :reviews, :restaurants) - 创建halal_items表的迁移需包含
t.references :restaurant, foreign_key: true(或add_foreign_key :halal_items, :restaurants) - 删除restaurants表中指向reviews/halal_items的反向外键(如果存在)
- 创建reviews表的迁移需包含
- 本地重新执行迁移:
rails db:drop db:create db:migrate,验证测试是否能复现问题(排除本地缓存干扰)
- 查看所有迁移文件,确保外键约束方向正确:
调整Github Action数据库初始化步骤:
将db:prepare改为db:migrate,确保迁移完全执行:- name: Setup database env: DATABASE_URL: **redacted** RAILS_ENV: test DISABLE_SPRING: 1 run: bundle exec rails db:migrate验证测试环境外键约束:
检查config/database.yml的test配置,确保PostgreSQL未关闭外键约束(默认开启,无需额外配置)。
内容的提问来源于stack exchange,提问作者Fritz Duverglas

