I have a state machine coded with the old and unmaintained state_machine gem (https://github.com/pluginaweek/state_machine).
Like in the examples, I have callbacks on transitions.
For example :
# 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
Now, I want to add some tests on my state machine but I need to mock the after_transition
calls.
I found a first solution somewhere but I don't like it because what's happening around transitions is less readable with this solution.
Instead of lines of codes in the after_transition
block do |object| ... end
, I put this lines of code in a method of an object (which can be, maybe a bit abusively, called an "observer") and call only this method in the after_transition
block :
# 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
Then, I only need to mock calls of the MyClassStateMachineObserver.on_any
and MyClassStateMachineObserver.go_to_toto
methods, which is an easy thing to do with Rspec.
With this first solution all my tests are green but my code is less readable.
After a LOT of researchs and debug sessions, I may have found a solution without modifying my state machine code :
# 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})
}
}
The solution come from reading the documentation and reading the tests of the state_machine gem.
state_machine Callback object has a Branch object as a read only instance variable. (https://github.com/pluginaweek/state_machine/blob/master/lib/state_machine/callback.rb#L107)
Branch object has an if_condition
as read only instance variable. (https://github.com/pluginaweek/state_machine/blob/master/lib/state_machine/branch.rb#L15)
If the result of the call to if_condition
is false, the callback seems to not be executed. (https://github.com/pluginaweek/state_machine/blob/master/test/unit/callback_test.rb#L290)
This second solution seems to mock correctly my callbacks but it seems to mock to many things because my tests are now red.
The states are not anymore played :/
Does someone know a good solution to mock this callbacks ?
I found no good response on this subject anywhere.
Jules