Shoulda test macro for acts_as_state_machine
In this post I will talk about shoulda macro for state_machine to facilitate TDD. So lets get familiar with these at first (want to skip the intro! ok, just jump to code ;)
Acts_as_state_machine:
A really powerful plugin to incorporate state machine in rails application. In scrumpad we are using this plugin and it made the implementation such a fun that everyone here just love to take this work 8). You can take a look to see how this plugin makes everything for you in a blink of eye.
Shoulda:
"The Shoulda gem makes it easy to write elegant, understandable, and maintainable Ruby tests"-Thoughtbot Community. Yes, shoulda really makes your test codes speak. You will be able to define your every test cases exactly in the way that you would probably state to someone.TDD:
Firstly programming without test code, impossible!! And, if your application is written in any dynamic language(e.g: ruby, php) then TDD is the must. In TDD practice at first you will define and write test codes for all your scenarios and definitely all these codes will fail. Then you will implement functionalities and make this test codes passed one by one.Get bored?? Dont worry!
Here is the cake with a cherry on top 8D
For example you have a Post model with three states -
draft, published and archived. And you want your post model to move in following way:| Action | Transitions |
| post.publish | :draft => :published |
| post.archive | :draft => :archived or :published => :archieved |
For now, lets work with above simplified transitions. So, lets move ahead to right the test codes first. Check following style...
class PostTest << ActionController::TestCase
should_acts_as_state_machine(:initial => :draft) do |config|
config[:states] = [:draft, :published, :archived]
config[:events] = {
:publish => {:draft => :published},
:archive => {:draft => :archived, :published => :archived}
}
end
end
Yes, you are done with your test code. Now write your
Post model and let it be acting as a state machine. The test code will assist you to make sure that your Post model have right number of states. And it moves between the states for right transitions. But before that.... do a little favor... copy the following code to the test_helper.rbActiveSupport::TestCase.class_eval do
class << self
def should_acts_as_state_machine(options)
options.reverse_merge!( :class => self.name.gsub('Test','').constantize )
expected_config = {:initial => options[:initial]}
yield expected_config
model_attributes = options[:class].inheritable_attributes
context 'State machine' do
should 'set initial' do
assert_equal(model_attributes[:initial_state], options[:initial], 'Initial state doesnt match')
end
should 'have identical states' do
assert_equal(expected_config[:states].size, model_attributes[:states].size, "Invalid number of states")
expected_config[:states].each do |state|
assert(model_attributes[:states].include?(state), "'#{state}' state not found")
end
end
should 'have identical transitions' do
assert_equal(expected_config[:events].size, model_attributes[:transition_table].size, 'Invalide numbers of events found')
end
should 'have events with appropriate transitions' do
expected_config[:events].each_key do |event|
expected_events = expected_config[:events][event]
actual_events = model_attributes[:transition_table][event]
assert_equal(expected_events.size, actual_events.size, "<#{event}> has different number of transitions")
expected_events.each_pair do |expected_from, expected_to|
detected = actual_events.detect{|transition| transition.from == expected_from and transition.to == expected_to }
assert_not_nil(detected, "From states are different for <#{event}> event")
end
end
end
end
end
end
end
This is the
should_acts_as_state_machine method that you called in your test code.Just another note. This method assumes that you named your test class by postfixed the
"Test" word to your model class name( PostTest for Post model). However if you are not doing in this way, just pass your class name (e,g. MyPost) like followingshould_acts_as_state_machine(:initial => :draft, :class => MyPost) do |config|
# set your configurations
end
So, what are you waiting for? Just give it a try and let me know your feedback :)
Like this post and I suggest you commit it to the shoulda project if possible. If thats a problem, you can create a plugin and commit to github so that more people gets to know about this.
ReplyDeleteThanks a lot vaia. I thought about create a plugin but then thought to make some more similar helpers and put all in a plugin. Anyway now i think its better to start with it. Thank you again.
ReplyDeleteYour post has been linked at www.DrinkRails.com
ReplyDelete