繁体   English   中英

RSpec模拟state_machine回调

[英]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_anyMyClassStateMachineObserver.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM