你怎么能在 rspec 中猴子修补控制器?
How can you monkey patch a controller in rspec?
场景
有一个并发可能导致重复键错误的竞争案例。举个例子:
def before_create_customer_by_external_id
end
def create_customer_from_external_id(external_id = nil)
@customer = current_account.customers.create!(external_id: external_id || @external_id)
end
def set_new_or_old_customer_by_external_id
if @customer.blank?
before_create_customer_by_external_id
create_customer_from_external_id
end
rescue ActiveRecord::RecordInvalid => e
raise e unless Customer.external_id_exception?(e)
@customer = current_account.customers.find_by_external_id(@external_id)
end
测试
现在,为了测试竞争情况(基于 Simulating race conditions in RSpec unit tests 的答案),我们只需要猴子补丁 before_create_customer_by_external_id
来调用 create_customer_from_external_id
.
问题
如何在不覆盖整个 class 并得到 "method not found" 错误的情况下执行此操作?
经过一番挖掘,我想出了以下解决方案:
context 'with race condition' do
it 'should hit race case and do what is expected' do
ControllerToOverride.class_eval do
def before_create_new_customer_by_external_id
create_customer_from_external_id
end
end
# ...expect...
ControllerToOverride.class_eval do
undef before_create_new_customer_by_external_id
end
end
end
我通过使用代码覆盖工具和调试语句验证了它是否符合竞争情况。
很高兴知道这里是否有更简洁的方法。
编辑 2020-04-24
根据评论,我们应该undef
此方法,以免影响后续测试。参考:https://medium.com/@scottradcliff/undefining-methods-in-ruby-eb7fba21f63f
我没有验证这一点,因为我不再有这个测试套件。如果 does/does 不起作用,请告诉我。
猴子修补 class 的下一步是创建一个 anonymous subclass:
context "with race condition" do
controller(ControllerToOverride) do
def before_create_customer_by_external_id
end
end
it "should deal with it " do
routes.draw { # define routes here }
...
end
end
这与您的解决方案并没有太大不同,但将 monkeypatch 与该上下文块隔离开来。
您可能不需要自定义路由块 - rspec 为其余方法(编辑、显示、索引等)设置一些虚拟路由
如果此上下文在 describe ControllerToOverride
块内,则控制器的参数是可选的,除非您已关闭 config.infer_base_class_for_anonymous_controllers
场景
有一个并发可能导致重复键错误的竞争案例。举个例子:
def before_create_customer_by_external_id
end
def create_customer_from_external_id(external_id = nil)
@customer = current_account.customers.create!(external_id: external_id || @external_id)
end
def set_new_or_old_customer_by_external_id
if @customer.blank?
before_create_customer_by_external_id
create_customer_from_external_id
end
rescue ActiveRecord::RecordInvalid => e
raise e unless Customer.external_id_exception?(e)
@customer = current_account.customers.find_by_external_id(@external_id)
end
测试
现在,为了测试竞争情况(基于 Simulating race conditions in RSpec unit tests 的答案),我们只需要猴子补丁 before_create_customer_by_external_id
来调用 create_customer_from_external_id
.
问题
如何在不覆盖整个 class 并得到 "method not found" 错误的情况下执行此操作?
经过一番挖掘,我想出了以下解决方案:
context 'with race condition' do
it 'should hit race case and do what is expected' do
ControllerToOverride.class_eval do
def before_create_new_customer_by_external_id
create_customer_from_external_id
end
end
# ...expect...
ControllerToOverride.class_eval do
undef before_create_new_customer_by_external_id
end
end
end
我通过使用代码覆盖工具和调试语句验证了它是否符合竞争情况。
很高兴知道这里是否有更简洁的方法。
编辑 2020-04-24
根据评论,我们应该undef
此方法,以免影响后续测试。参考:https://medium.com/@scottradcliff/undefining-methods-in-ruby-eb7fba21f63f
我没有验证这一点,因为我不再有这个测试套件。如果 does/does 不起作用,请告诉我。
猴子修补 class 的下一步是创建一个 anonymous subclass:
context "with race condition" do
controller(ControllerToOverride) do
def before_create_customer_by_external_id
end
end
it "should deal with it " do
routes.draw { # define routes here }
...
end
end
这与您的解决方案并没有太大不同,但将 monkeypatch 与该上下文块隔离开来。
您可能不需要自定义路由块 - rspec 为其余方法(编辑、显示、索引等)设置一些虚拟路由
如果此上下文在 describe ControllerToOverride
块内,则控制器的参数是可选的,除非您已关闭 config.infer_base_class_for_anonymous_controllers