-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Documentation - How to test Statesman implementations #77
Comments
Hey @pacso, We do test the implementation of our models that use our state machines, just with regular Rspec matchers, not with any custom ones. We definitely should have a sample test in our README though, will try to get that added. On a rough level, our specs tend to look like: describe SomeModel do
let(:some_model) { FactoryGirl.build(:some_model) }
it "is in state foo by default" do
expect(some_model.current_state).to eq("foo")
end
describe "guards" do
it "cannot transition from state foo to state bar" do
expect { some_model.transition_to!(:bar) }.to raise_error(Statesman::GuardFailedError)
end
it "can transition from state foo to state baz" do
expect { some_model.transition_to!(:baz).to_not raise_error
end
end
end So we test our guards pretty heavily by just making sure that Does that help a little? Jack |
Hi Jack, Thanks for the reply - that's quite useful. The only thing I need now is a method for creating a model in a particular state. For example, imagine a complex state machine where there are many branching paths and guards throughout to control the transitions ... how would you instantiate an object somewhere in the middle of that without manually generating the Transition record for the previous state? |
It's not something we've had to do, so there isn't really great support for it right now. The two options for this are to do what you suggest and manually generate the transition, or you could define a method to step through the states. Neither is especially neat unfortunately, I suppose it depends on your data which you'd rather do. |
I've come up with a reasonably neat solution. I'd be interested to hear your thoughts. I have an OrderItem model which has a state machine to manage the process of handling an order. Using FactoryGirl I now have factories along the following lines: FactoryGirl.define do
factory :order_item_transition do
to_state :unassigned
sort_key 0
order_item
end
factory :order_item do
ignore do
current_state :unordered
end
after(:create) do |order_item, evaluator|
if evaluator.current_state != :unordered
create :order_item_transition, to_state: evaluator.current_state, order_item: order_item
end
end
end
end This allows me to create an OrderItem within a spec with any required state from my state machine: RSpec.describe OrderItem, :type => :model do
describe 'transitions' do
it 'is in state unordered by default' do
expect(build(:order_item).current_state).to eq 'unordered'
end
it 'transitions from unassigned to order_required upon assignment event' do
order_item = create(:order_item, current_state: :unassigned)
expect { order_item.trigger!(:assign_to_orderer) }.to change { order_item.current_state }.from('unassigned').to('order_required')
end
end
end Doesn't seem too messy. Maybe others would find this useful. |
@pacso we can't really do that because we have such tight guards on our state machines, due to the nature of what we're modelling. If we wanted to transition through we'd have to go one at a time. Your solution seems neat could you also post up the source for the state machine? I think this would make a nice addition to the README 👍 |
@pacso We mock state machine statuses like this (for factory :order do
property "value"
...
trait :shipped do
after(:create) do |order|
FactoryGirl.create(:order_transition, :shipped, order: order)
end
end
end
factory :order_transition do
order
...
trait :shipped do
to_state "shipped"
end
end This gets around the transtition guards since there are no guards on creating a transition record, only performing the transition. You could then do: let(:shipped_order) { FactoryGirl.create(:order, :shipped) }
it "transitions to returned" do
expect { shipped_order.transition_to!(:returned) }.to_not raise_exception
end |
We have now added some of this to the README. @pacso if there's anything we missed, please comment and let us know, and thanks for raising this issue 👍 |
Hey @PasCo @jackfranklin would you guys mind posting the contents of one of your |
Hi @mecampbellsoup - the FactoryGirl solution I mentioned above is actually what I'm still using. It provides enough to be able to unit test my state machine. For integration tests I don't use any factories for orders or transitions. |
Oh okay. So the factory must be filling in defaults for those On Thu, Jan 21, 2016, 5:41 PM Jon Pascoe [email protected] wrote:
|
@mecampbellsoup You can just make I find this to be a fairly decent solution to bypassing guards and just putting the model into some state in order to be able to test business logic. Thanks @pacso! |
Hello,
Considering this is quite a new project, it's no surprise that the documentation is a little thin. There's enough to get you going with a working state machine, however there's no mention of the best way to test states/transitions/guards/etc from within an application that uses statesman.
I'm more than happy to help contribute towards the documentation, but just wondered if you have any details on how you currently test models which have a statesman state machine behind them? Do you have any RSpec custom matchers? Or do you not test the implementation of your state machines in a unit-test style but rather in functional tests?
The text was updated successfully, but these errors were encountered: