使用RSpec测试环境依赖路由遇问题,求排查方案
解决Rails环境依赖路由的RSpec测试问题
这个问题的核心在于Rails路由是在应用初始化阶段就编译完成的,你在测试中通过allow(Rails).to receive(:env) mock环境变量时,路由已经加载完毕,所以这个mock根本不会改变已有的路由定义——这就是为什么两个测试都失败的原因:
为什么你的测试会失败?
- 生产环境测试失败:当路由中没有
new_development_account的GET路由时,Rails会把/ims/users/new_development_account匹配到users#show(把new_development_account当作:id参数),所以be_routable会返回true,但你期望它是不可路由的,因此测试失败。 - 开发环境测试失败:RSpec默认使用
test环境运行,启动时路由并没有加载那些仅在development环境下的路由。即使你mock了Rails.env为development,已经编译好的路由不会重新解析,所以POST路由根本不存在,be_routable返回false,测试失败。
解决方案:抽离路由开关 + 动态重载路由
我们可以把路由的环境依赖改成一个可配置的开关,而不是直接依赖Rails.env,这样在测试中可以灵活控制,并且手动重载路由。
步骤1:修改路由定义和环境配置
首先修改config/routes.rb,把环境判断换成配置项:
# config/routes.rb resources :users do collection do # ... 你的其他用户集合路由 if Rails.application.config.allow_development_user_routes get :new_development_account post :create_development_account end end end
然后在对应的环境配置文件中设置开关:
# config/environments/development.rb Rails.application.configure do # ... 其他配置 config.allow_development_user_routes = true end
# config/environments/production.rb Rails.application.configure do # ... 其他配置 config.allow_development_user_routes = false end
# config/environments/test.rb Rails.application.configure do # ... 其他配置 config.allow_development_user_routes = false # 测试环境默认关闭 end
步骤2:修改RSpec测试,动态控制开关并重载路由
在测试中,我们需要修改配置后手动调用reload_routes!来重新编译路由,并且在测试前后恢复原始配置,避免影响其他测试:
require 'spec_helper' describe "routes for users controller", :type => :routing do # 保存原始配置,测试后恢复 let(:original_route_config) { Rails.application.config.allow_development_user_routes } around(:each) do |example| example.run # 恢复原始配置并重载路由 Rails.application.config.allow_development_user_routes = original_route_config Rails.application.reload_routes! end context "production environment" do it "blocks development-only routes" do Rails.application.config.allow_development_user_routes = false Rails.application.reload_routes! # 注意:这里用not_to route_to而不是not_to be_routable,避免误判show路由 expect(:get => "/ims/users/new_development_account").not_to route_to( controller: "ims/users", action: "new_development_account" ) expect(:post => "/ims/users/create_development_account").not_to be_routable end end context "development environment" do it "exposes development-only routes" do Rails.application.config.allow_development_user_routes = true Rails.application.reload_routes! expect(:get => "/ims/users/new_development_account").to route_to( controller: "ims/users", action: "new_development_account" ) expect(:post => "/ims/users/create_development_account").to route_to( controller: "ims/users", action: "create_development_account" ) end end end
额外提示
- 尽量用
route_to匹配器代替be_routable:be_routable只要有任何路由能匹配路径就会返回true(比如前面提到的users#show),而route_to可以精确匹配到指定的controller和action,测试结果更准确。 - 测试后一定要恢复配置和路由:避免这个测试的配置修改影响其他测试用例的执行。
内容的提问来源于stack exchange,提问作者Tara Livesey
相关产品推荐
相关产品推荐

