[英]Rspec mock state_machine callbacks
我有一台状态机,上面编码着旧的且未维护的state_machine gem( https://github.com/pluginaweek/state_machine )。
像示例中一样,我在过渡上也有回调。
例如 :
# my_class.rb
state_machine :state_machine_name, :initial => :initial_state do
event :go_to_toto do
transition :initial_state => :toto
end
event :from_toto_to_tata do
transition :toto => :tata
end
...
after_transition :on => any do |person|
# dumb code for example purpose only
log.info(person.name)
say_hello(person)
say_goodbye(person)
...
end
after_transition :on => :go_to_toto do |person, transition|
# again, dumb code for example purpose only
send_mail(person)
call(person)
...
end
end
现在,我想在状态机上添加一些测试,但是我需要模拟after_transition
调用。
我在某处找到了第一个解决方案,但我不喜欢它,因为在该解决方案周围,转换所发生的事情难以读取。
do |object| ... end
代替after_transition
块中的代码行 do |object| ... end
,我将这行代码放在对象的方法中(可能有点叫作“观察者”),并在after_transition
块中仅调用此方法:
# my_class.rb
state_machine :state_machine_name, :initial => :initial_state do
event :go_to_toto do
transition :initial_state => :toto
end
event :from_toto_to_tata do
transition :toto => :tata
end
...
after_transition :on => any do |person|
MyClassStateMachineObserver.on_any(person)
end
after_transition :on => :go_to_toto do |person, transition|
MyClassStateMachineObserver.go_to_toto(person)
end
end
# my_class_state_machine_observer.rb
class MyClassStateMachineObserver
def self.on_any(person)
# dumb code for example purpose only
log.info(person.name)
say_hello(person)
say_goodbye(person)
...
end
def self.go_to_toto(person)
# again, dumb code for example purpose only
send_mail(person)
call(person)
...
end
end
然后,我只需要模拟MyClassStateMachineObserver.on_any
和MyClassStateMachineObserver.go_to_toto
方法的调用,这MyClassStateMachineObserver.on_any
MyClassStateMachineObserver.go_to_toto
来说很容易。
使用第一个解决方案,我所有的测试都是绿色的,但是我的代码可读性较差。
经过大量的研究和调试会议,我可能找到了无需修改状态机代码的解决方案:
# my_class_spec.rb
let!(:mocks) {
MyClass.state_machines[:state_machine_name].callbacks.flat_map{ |k, callbackArray| callbackArray }.map{ |callback|
allow(callback.branch).to receive(:if_condition).and_return(lambda {false})
}
}
解决方案来自阅读文档和阅读state_machine gem的测试。
state_machine回调对象具有一个Branch对象作为只读实例变量。 ( https://github.com/pluginaweek/state_machine/blob/master/lib/state_machine/callback.rb#L107 )
分支对象具有if_condition
作为只读实例变量。 ( https://github.com/pluginaweek/state_machine/blob/master/lib/state_machine/branch.rb#L15 )
如果对if_condition
的调用结果为false,则似乎未执行该回调。 ( https://github.com/pluginaweek/state_machine/blob/master/test/unit/callback_test.rb#L290 )
第二种解决方案似乎可以正确模拟我的回调,但是它可以模拟许多事情,因为我的测试现在是红色的。
状态不再播放:/
有人知道模拟此回调的好方法吗?
我在任何地方都没有对此问题做出任何好的回应。
朱尔斯
好的,我终于找到了解决方案。
这是解决方案:
MyClass
.state_machines[:state_machine_name]
.callbacks
.flat_map{ |k, callbackArray| callbackArray }
.find_all{ |callback|
callback.instance_variable_get('@methods').any? { |callback_method_proc|
/my_class/.match callback_method_proc.to_s
}
}.map{ |our_callback|
allow(our_callback.branch).to receive(:if_condition).and_return(lambda {false})
}
这里有一些关于此代码的解释:
首先,我得到了回调。 这是一个Map,其中key是回调类型(:before,:after,:around,:failure),值是回调数组:
MyClass
.state_machines[:state_machine_name]
.callbacks
然后,我将地图展平以获取所有回调的数组。 如果要过滤所有回调类型,则无需保留回调类型:
MyClass
.state_machines[:state_machine_name]
.callbacks
.flat_map{ |k, callbackArray| callbackArray }
然后(这里有些棘手),我过滤了回调以仅保留在状态机中声明的回调。
state_machine gem在状态机中添加一些回调以完成其工作。
因此,有效地,在第一篇文章的第二种解决方案中,我嘲笑了太多的回调(state_machine gem回调和我的)。
为了确定哪个Callback是我的Callback,经过一些研究,我发现Callback由包含@methods
私有实例变量组成。
每个Proc引用均包含包含其代码的文件的名称。
因此,我仅保留@method
引用中包含状态机代码的文件名称的回调(丑陋的技巧,我知道;)):
MyClass
.state_machines[:state_machine_name]
.callbacks
.flat_map{ |k, callbackArray| callbackArray }
.find_all{ |callback|
callback.instance_variable_get('@methods').any? { |callback_method_proc|
/my_class/.match callback_method_proc.to_s
}
}
最后,我禁止回调调用:
MyClass
.state_machines[:state_machine_name]
.callbacks
.flat_map{ |k, callbackArray| callbackArray }
.find_all{ |callback|
callback.instance_variable_get('@methods').any? { |callback_method_proc|
/my_class/.match callback_method_proc.to_s
}
}.map{ |our_callback|
allow(our_callback.branch).to receive(:if_condition).and_return(lambda {false})
}
我写了一个更通用的帮助器类,它可以模拟任何状态机回调和任何状态机回调类型(:before,:after,:around,:failure):
https://gist.github.com/guizmaii/d8571351557ac1e94561
问候,
朱尔斯
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.