[DRE-commits] [ruby-state-machines] 01/02: Imported Upstream version 0.4.0

Abhijith PA abhijithpa-guest at moszumanska.debian.org
Tue Nov 10 17:33:15 UTC 2015


This is an automated email from the git hooks/post-receive script.

abhijithpa-guest pushed a commit to branch master
in repository ruby-state-machines.

commit c26d885bb2e3d7b8c5b70cd4fae49f126c8a17a4
Author: Abhijith PA <abhijith at openmailbox.org>
Date:   Tue Nov 10 22:58:06 2015 +0530

    Imported Upstream version 0.4.0
---
 .gitignore                                         |   21 +
 .rspec                                             |    3 +
 .travis.yml                                        |   14 +
 Changelog.md                                       |   16 +
 Contributors.md                                    |   39 +
 Gemfile                                            |    8 +
 LICENSE.txt                                        |   23 +
 README.md                                          |  601 ++++++
 Rakefile                                           |   12 +
 Testing.md                                         |    0
 lib/state_machines.rb                              |    3 +
 lib/state_machines/assertions.rb                   |   40 +
 lib/state_machines/branch.rb                       |  183 ++
 lib/state_machines/callback.rb                     |  220 ++
 lib/state_machines/core.rb                         |   43 +
 lib/state_machines/core_ext.rb                     |    2 +
 lib/state_machines/core_ext/class/state_machine.rb |    3 +
 lib/state_machines/error.rb                        |  112 +
 lib/state_machines/eval_helpers.rb                 |   87 +
 lib/state_machines/event.rb                        |  228 ++
 lib/state_machines/event_collection.rb             |  139 ++
 lib/state_machines/extensions.rb                   |  148 ++
 lib/state_machines/helper_module.rb                |   17 +
 lib/state_machines/integrations.rb                 |  116 +
 lib/state_machines/integrations/base.rb            |   44 +
 lib/state_machines/machine.rb                      | 2230 ++++++++++++++++++++
 lib/state_machines/machine_collection.rb           |   96 +
 lib/state_machines/macro_methods.rb                |  520 +++++
 lib/state_machines/matcher.rb                      |  121 ++
 lib/state_machines/matcher_helpers.rb              |   54 +
 lib/state_machines/node_collection.rb              |  219 ++
 lib/state_machines/path.rb                         |  120 ++
 lib/state_machines/path_collection.rb              |   88 +
 lib/state_machines/state.rb                        |  272 +++
 lib/state_machines/state_collection.rb             |  110 +
 lib/state_machines/state_context.rb                |  133 ++
 lib/state_machines/transition.rb                   |  414 ++++
 lib/state_machines/transition_collection.rb        |  246 +++
 lib/state_machines/version.rb                      |    3 +
 metadata.yml                                       |  965 +++++++++
 state_machines.gemspec                             |   23 +
 .../integrations/event_on_failure_integration.rb   |   10 +
 test/files/integrations/vehicle.rb                 |    7 +
 test/files/models/auto_shop.rb                     |   31 +
 test/files/models/car.rb                           |   21 +
 test/files/models/model_base.rb                    |    6 +
 test/files/models/motorcycle.rb                    |   11 +
 test/files/models/traffic_light.rb                 |   47 +
 test/files/models/vehicle.rb                       |  127 ++
 test/files/node.rb                                 |    5 +
 test/files/switch.rb                               |   15 +
 test/functional/auto_shop_available_test.rb        |   20 +
 test/functional/auto_shop_busy_test.rb             |   25 +
 test/functional/car_backing_up_test.rb             |   45 +
 test/functional/car_test.rb                        |   49 +
 test/functional/motorcycle_test.rb                 |   46 +
 test/functional/traffic_light_caution_test.rb      |   17 +
 test/functional/traffic_light_proceed_test.rb      |   17 +
 test/functional/traffic_light_stop_test.rb         |   26 +
 test/functional/vehicle_first_gear_test.rb         |   42 +
 test/functional/vehicle_idling_test.rb             |   59 +
 test/functional/vehicle_locked_test.rb             |   29 +
 test/functional/vehicle_parked_test.rb             |   53 +
 test/functional/vehicle_repaired_test.rb           |   20 +
 test/functional/vehicle_second_gear_test.rb        |   42 +
 test/functional/vehicle_stalled_test.rb            |   65 +
 test/functional/vehicle_test.rb                    |   20 +
 test/functional/vehicle_third_gear_test.rb         |   42 +
 test/functional/vehicle_unsaved_test.rb            |  181 ++
 .../vehicle_with_event_attributes_test.rb          |   30 +
 .../vehicle_with_parallel_events_test.rb           |   36 +
 test/test_helper.rb                                |   15 +
 test/unit/assertions/assert_exclusive_keys_test.rb |   22 +
 test/unit/assertions/assert_valid_key_test.rb      |   12 +
 test/unit/branch/branch_test.rb                    |   28 +
 .../branch_with_conflicting_conditionals_test.rb   |   27 +
 ...anch_with_conflicting_from_requirements_test.rb |    8 +
 ...branch_with_conflicting_on_requirements_test.rb |    8 +
 ...branch_with_conflicting_to_requirements_test.rb |    8 +
 .../branch_with_different_requirements_test.rb     |   41 +
 ...ch_with_except_from_matcher_requirement_test.rb |    8 +
 .../branch_with_except_from_requirement_test.rb    |   36 +
 ...anch_with_except_on_matcher_requirement_test.rb |    8 +
 .../branch_with_except_on_requirement_test.rb      |   36 +
 ...anch_with_except_to_matcher_requirement_test.rb |    8 +
 .../branch_with_except_to_requirement_test.rb      |   36 +
 .../branch_with_from_matcher_requirement_test.rb   |   20 +
 .../branch/branch_with_from_requirement_test.rb    |   45 +
 .../unit/branch/branch_with_if_conditional_test.rb |   27 +
 ...with_implicit_and_explicit_requirements_test.rb |   23 +
 ..._with_implicit_from_requirement_matcher_test.rb |   20 +
 .../branch_with_implicit_requirement_test.rb       |   20 +
 ...ch_with_implicit_to_requirement_matcher_test.rb |   16 +
 ..._with_multiple_except_from_requirements_test.rb |   20 +
 ...ch_with_multiple_except_on_requirements_test.rb |   16 +
 ...ch_with_multiple_except_to_requirements_test.rb |   20 +
 .../branch_with_multiple_from_requirements_test.rb |   16 +
 .../branch_with_multiple_if_conditionals_test.rb   |   20 +
 ...nch_with_multiple_implicit_requirements_test.rb |   53 +
 .../branch_with_multiple_on_requirements_test.rb   |   20 +
 .../branch_with_multiple_to_requirements_test.rb   |   20 +
 ...ranch_with_multiple_unless_conditionals_test.rb |   20 +
 .../branch/branch_with_nil_requirements_test.rb    |   28 +
 .../branch/branch_with_no_requirements_test.rb     |   36 +
 .../branch_with_on_matcher_requirement_test.rb     |   16 +
 .../unit/branch/branch_with_on_requirement_test.rb |   45 +
 .../branch_with_to_matcher_requirement_test.rb     |   20 +
 .../unit/branch/branch_with_to_requirement_test.rb |   45 +
 .../branch/branch_with_unless_conditional_test.rb  |   27 +
 test/unit/branch/branch_without_guards_test.rb     |   27 +
 test/unit/callback/callback_by_default_test.rb     |   25 +
 test/unit/callback/callback_test.rb                |   53 +
 .../callback_with_application_bound_object_test.rb |   23 +
 .../callback_with_application_terminator_test.rb   |   24 +
 test/unit/callback/callback_with_arguments_test.rb |   14 +
 ...callback_with_around_type_and_arguments_test.rb |   25 +
 .../callback_with_around_type_and_block_test.rb    |   44 +
 ...lback_with_around_type_and_bound_method_test.rb |   23 +
 ...k_with_around_type_and_multiple_methods_test.rb |   93 +
 ...allback_with_around_type_and_terminator_test.rb |   17 +
 test/unit/callback/callback_with_block_test.rb     |   20 +
 ...allback_with_bound_method_and_arguments_test.rb |   28 +
 .../callback/callback_with_bound_method_test.rb    |   35 +
 test/unit/callback/callback_with_do_method_test.rb |   18 +
 .../callback_with_explicit_requirements_test.rb    |   32 +
 .../callback/callback_with_if_condition_test.rb    |   17 +
 .../callback_with_implicit_requirements_test.rb    |   32 +
 .../callback/callback_with_method_argument_test.rb |   18 +
 .../callback/callback_with_mixed_methods_test.rb   |   31 +
 .../callback_with_multiple_bound_methods_test.rb   |   21 +
 .../callback_with_multiple_do_methods_test.rb      |   29 +
 ...callback_with_multiple_method_arguments_test.rb |   29 +
 .../unit/callback/callback_with_terminator_test.rb |   22 +
 .../callback/callback_with_unbound_method_test.rb  |   14 +
 .../callback_with_unless_condition_test.rb         |   17 +
 .../callback/callback_without_arguments_test.rb    |   14 +
 .../callback/callback_without_terminator_test.rb   |   12 +
 test/unit/error/error_by_default_test.rb           |   21 +
 test/unit/error/error_with_message_test.rb         |   23 +
 test/unit/eval_helper/eval_helpers_base_test.rb    |    8 +
 ...lpers_proc_block_and_explicit_arguments_test.rb |   14 +
 ...lpers_proc_block_and_implicit_arguments_test.rb |   14 +
 test/unit/eval_helper/eval_helpers_proc_test.rb    |   13 +
 .../eval_helpers_proc_with_arguments_test.rb       |   13 +
 .../eval_helpers_proc_with_block_test.rb           |   13 +
 ...lpers_proc_with_block_without_arguments_test.rb |   18 +
 ..._helpers_proc_with_block_without_object_test.rb |   14 +
 .../eval_helpers_proc_without_arguments_test.rb    |   19 +
 test/unit/eval_helper/eval_helpers_string_test.rb  |   25 +
 .../eval_helpers_string_with_block_test.rb         |   12 +
 .../eval_helpers_symbol_method_missing_test.rb     |   20 +
 .../eval_helpers_symbol_private_test.rb            |   17 +
 .../eval_helpers_symbol_protected_test.rb          |   17 +
 .../eval_helpers_symbol_tainted_method_test.rb     |   18 +
 test/unit/eval_helper/eval_helpers_symbol_test.rb  |   16 +
 ...helpers_symbol_with_arguments_and_block_test.rb |   16 +
 .../eval_helpers_symbol_with_arguments_test.rb     |   16 +
 .../eval_helpers_symbol_with_block_test.rb         |   16 +
 test/unit/eval_helper/eval_helpers_test.rb         |   13 +
 test/unit/event/event_after_being_copied_test.rb   |   17 +
 test/unit/event/event_by_default_test.rb           |   60 +
 test/unit/event/event_context_test.rb              |   16 +
 test/unit/event/event_on_failure_test.rb           |   44 +
 test/unit/event/event_test.rb                      |   34 +
 test/unit/event/event_transitions_test.rb          |   62 +
 ...th_conflicting_helpers_after_definition_test.rb |   79 +
 ...h_conflicting_helpers_before_definition_test.rb |   58 +
 .../event/event_with_conflicting_machine_test.rb   |   48 +
 .../event/event_with_dynamic_human_name_test.rb    |   26 +
 test/unit/event/event_with_human_name_test.rb      |   13 +
 .../event/event_with_invalid_current_state_test.rb |   30 +
 test/unit/event/event_with_machine_action_test.rb  |   33 +
 test/unit/event/event_with_marshalling_test.rb     |   47 +
 ...vent_with_matching_disabled_transitions_test.rb |  115 +
 ...event_with_matching_enabled_transitions_test.rb |   75 +
 .../event/event_with_multiple_transitions_test.rb  |   61 +
 test/unit/event/event_with_namespace_test.rb       |   34 +
 ...th_transition_with_blacklisted_to_state_test.rb |   60 +
 ...ent_with_transition_with_loopback_state_test.rb |   36 +
 ...event_with_transition_with_nil_to_state_test.rb |   36 +
 ...th_transition_with_whitelisted_to_state_test.rb |   51 +
 .../event_with_transition_without_to_state_test.rb |   36 +
 test/unit/event/event_with_transitions_test.rb     |   32 +
 .../event_without_matching_transitions_test.rb     |   41 +
 test/unit/event/event_without_transitions_test.rb  |   28 +
 test/unit/event/invalid_event_test.rb              |   20 +
 ...ollection_attribute_with_machine_action_test.rb |   62 +
 ...ction_attribute_with_namespaced_machine_test.rb |   36 +
 .../event_collection_by_default_test.rb            |   26 +
 .../unit/event_collection/event_collection_test.rb |   39 +
 ...ollection_with_custom_machine_attribute_test.rb |   31 +
 ...collection_with_events_with_transitions_test.rb |   76 +
 .../event_collection_with_multiple_events_test.rb  |   27 +
 .../event_collection_with_validations_test.rb      |   74 +
 ...event_collection_without_machine_action_test.rb |   18 +
 .../event_string_collection_test.rb                |   31 +
 test/unit/helper_module_test.rb                    |   17 +
 test/unit/integrations/integration_finder_test.rb  |   16 +
 test/unit/integrations/integration_matcher_test.rb |   27 +
 .../invalid_parallel_transition_test.rb            |   18 +
 .../invalid_transition/invalid_transition_test.rb  |   47 +
 .../invalid_transition_with_integration_test.rb    |   45 +
 .../invalid_transition_with_namespace_test.rb      |   32 +
 .../machine/machine_after_being_copied_test.rb     |   62 +
 .../machine_after_changing_initial_state.rb        |   28 +
 .../machine_after_changing_owner_class_test.rb     |   31 +
 test/unit/machine/machine_by_default_test.rb       |  160 ++
 .../machine/machine_finder_custom_options_test.rb  |   17 +
 ...der_with_existing_machine_on_superclass_test.rb |   85 +
 ...hine_finder_with_existing_on_same_class_test.rb |   23 +
 ...machine_finder_without_existing_machine_test.rb |   25 +
 test/unit/machine/machine_persistence_test.rb      |   52 +
 .../machine/machine_state_initialization_test.rb   |   56 +
 test/unit/machine/machine_test.rb                  |   30 +
 .../machine_with_action_already_overridden_test.rb |   23 +
 .../machine_with_action_defined_in_class_test.rb   |   37 +
 ..._with_action_defined_in_included_module_test.rb |   46 +
 ...chine_with_action_defined_in_superclass_test.rb |   43 +
 .../machine/machine_with_action_undefined_test.rb  |   33 +
 .../unit/machine/machine_with_cached_state_test.rb |   20 +
 .../machine/machine_with_class_helpers_test.rb     |  179 ++
 ...th_conflicting_helpers_after_definition_test.rb |  244 +++
 ...h_conflicting_helpers_before_definition_test.rb |  175 ++
 .../machine/machine_with_custom_action_test.rb     |   11 +
 .../machine/machine_with_custom_attribute_test.rb  |  103 +
 .../machine/machine_with_custom_initialize_test.rb |   24 +
 .../machine_with_custom_integration_test.rb        |   72 +
 .../machine_with_custom_invalidation_test.rb       |   39 +
 test/unit/machine/machine_with_custom_name_test.rb |   57 +
 .../machine/machine_with_custom_plural_test.rb     |   52 +
 .../machine_with_dynamic_initial_state_test.rb     |   65 +
 .../machine/machine_with_event_matchers_test.rb    |   41 +
 test/unit/machine/machine_with_events_test.rb      |   52 +
 ...ine_with_events_with_custom_human_names_test.rb |   18 +
 .../machine_with_events_with_transitions_test.rb   |   37 +
 .../machine/machine_with_existing_event_test.rb    |   17 +
 ...e_with_existing_machines_on_owner_class_test.rb |   20 +
 ...nes_with_same_attributes_on_owner_class_test.rb |   71 +
 ..._with_same_attributes_on_owner_subclass_test.rb |   31 +
 .../machine/machine_with_existing_state_test.rb    |   27 +
 .../machine/machine_with_failure_callbacks_test.rb |   48 +
 test/unit/machine/machine_with_helpers_test.rb     |   14 +
 ...h_initial_state_with_value_and_owner_default.rb |   25 +
 .../machine_with_initialize_and_super_test.rb      |   17 +
 ...ine_with_initialize_arguments_and_block_test.rb |   31 +
 .../machine_with_initialize_without_super_test.rb  |   17 +
 .../machine/machine_with_instance_helpers_test.rb  |  179 ++
 test/unit/machine/machine_with_integration_test.rb |   72 +
 .../machine/machine_with_multiple_events_test.rb   |   32 +
 test/unit/machine/machine_with_namespace_test.rb   |   48 +
 test/unit/machine/machine_with_nil_action_test.rb  |   27 +
 test/unit/machine/machine_with_other_states.rb     |   22 +
 .../machine/machine_with_owner_subclass_test.rb    |   18 +
 test/unit/machine/machine_with_paths_test.rb       |   25 +
 .../machine/machine_with_private_action_test.rb    |   43 +
 .../machine/machine_with_state_matchers_test.rb    |   41 +
 .../machine_with_state_with_matchers_test.rb       |   19 +
 test/unit/machine/machine_with_states_test.rb      |   55 +
 .../machine_with_states_with_behaviors_test.rb     |   23 +
 ...ine_with_states_with_custom_human_names_test.rb |   18 +
 .../machine_with_states_with_custom_values_test.rb |   21 +
 ...e_with_states_with_runtime_dependencies_test.rb |   19 +
 .../machine_with_static_initial_state_test.rb      |   49 +
 ...ss_conflicting_helpers_after_definition_test.rb |   36 +
 .../machine_with_transition_callbacks_test.rb      |  144 ++
 test/unit/machine/machine_with_transitions_test.rb |   87 +
 .../machine/machine_without_initialization_test.rb |   31 +
 .../machine/machine_without_initialize_test.rb     |   14 +
 .../machine/machine_without_integration_test.rb    |   31 +
 .../machine_collection_by_default_test.rb          |   11 +
 ...ection_fire_attributes_with_validations_test.rb |   72 +
 .../machine_collection_fire_test.rb                |   80 +
 ...chine_collection_fire_with_transactions_test.rb |   54 +
 ...achine_collection_fire_with_validations_test.rb |   76 +
 ...machine_collection_state_initialization_test.rb |  111 +
 ...ollection_transitions_with_blank_events_test.rb |   25 +
 ...lection_transitions_with_custom_options_test.rb |   20 +
 ...tion_transitions_with_different_actions_test.rb |   26 +
 ..._transitions_with_exisiting_transitions_test.rb |   25 +
 ...lection_transitions_with_invalid_events_test.rb |   25 +
 ...ollection_transitions_with_same_actions_test.rb |   31 +
 ..._collection_transitions_with_transition_test.rb |   26 +
 ...e_collection_transitions_without_events_test.rb |   25 +
 ...llection_transitions_without_transition_test.rb |   27 +
 test/unit/matcher/all_matcher_test.rb              |   29 +
 test/unit/matcher/blacklist_matcher_test.rb        |   30 +
 test/unit/matcher/loopback_matcher_test.rb         |   27 +
 test/unit/matcher/matcher_by_default_test.rb       |   15 +
 .../matcher/matcher_with_multiple_values_test.rb   |   15 +
 test/unit/matcher/matcher_with_value_test.rb       |   15 +
 test/unit/matcher/whitelist_matcher_test.rb        |   30 +
 .../matcher_helpers/matcher_helpers_all_test.rb    |   14 +
 .../matcher_helpers/matcher_helpers_any_test.rb    |   14 +
 .../matcher_helpers/matcher_helpers_same_test.rb   |   13 +
 .../node_collection_after_being_copied_test.rb     |   46 +
 .../node_collection_after_update_test.rb           |   36 +
 .../node_collection_by_default_test.rb             |   22 +
 test/unit/node_collection/node_collection_test.rb  |   23 +
 .../node_collection_with_indices_test.rb           |   42 +
 .../node_collection_with_matcher_contexts_test.rb  |   25 +
 .../node_collection_with_nodes_test.rb             |   46 +
 .../node_collection_with_numeric_index_test.rb     |   24 +
 ...de_collection_with_postdefined_contexts_test.rb |   22 +
 ...ode_collection_with_predefined_contexts_test.rb |   23 +
 .../node_collection_with_string_index_test.rb      |   20 +
 .../node_collection_with_symbol_index_test.rb      |   20 +
 .../node_collection_without_indices_test.rb        |   30 +
 test/unit/path/path_by_default_test.rb             |   54 +
 test/unit/path/path_test.rb                        |   14 +
 ...lable_transitions_after_reaching_target_test.rb |   40 +
 .../path/path_with_available_transitions_test.rb   |   54 +
 .../path/path_with_deep_target_reached_test.rb     |   50 +
 test/unit/path/path_with_deep_target_test.rb       |   40 +
 test/unit/path/path_with_duplicates_test.rb        |   32 +
 .../path/path_with_encountered_transitions_test.rb |   34 +
 .../path/path_with_guarded_transitions_test.rb     |   42 +
 test/unit/path/path_with_reached_target_test.rb    |   35 +
 test/unit/path/path_with_transitions_test.rb       |   54 +
 test/unit/path/path_with_unreached_target_test.rb  |   31 +
 test/unit/path/path_without_transitions_test.rb    |   24 +
 .../path_collection_by_default_test.rb             |   46 +
 test/unit/path_collection/path_collection_test.rb  |   24 +
 .../path_collection_with_deep_paths_test.rb        |   43 +
 .../path_collection_with_duplicate_nodes_test.rb   |   31 +
 .../path_collection_with_from_state_test.rb        |   27 +
 .../path_collection_with_paths_test.rb             |   47 +
 .../path_collection_with_to_state_test.rb          |   29 +
 .../path_with_guarded_paths_test.rb                |   25 +
 test/unit/state/state_after_being_copied_test.rb   |   19 +
 test/unit/state/state_by_default_test.rb           |   41 +
 test/unit/state/state_final_test.rb                |   28 +
 test/unit/state/state_initial_test.rb              |   13 +
 test/unit/state/state_not_final_test.rb            |   32 +
 test/unit/state/state_not_initial_test.rb          |   13 +
 test/unit/state/state_test.rb                      |   44 +
 .../state/state_with_cached_lambda_value_test.rb   |   29 +
 ...th_conflicting_helpers_after_definition_test.rb |   38 +
 ...h_conflicting_helpers_before_definition_test.rb |   29 +
 .../state_with_conflicting_machine_name_test.rb    |   20 +
 .../state/state_with_conflicting_machine_test.rb   |   37 +
 test/unit/state/state_with_context_test.rb         |   60 +
 .../state/state_with_dynamic_human_name_test.rb    |   25 +
 .../state_with_existing_context_method_test.rb     |   24 +
 test/unit/state/state_with_human_name_test.rb      |   13 +
 test/unit/state/state_with_integer_value_test.rb   |   32 +
 .../state/state_with_invalid_method_call_test.rb   |   21 +
 test/unit/state/state_with_lambda_value_test.rb    |   37 +
 test/unit/state/state_with_matcher_test.rb         |   18 +
 .../state/state_with_multiple_contexts_test.rb     |   57 +
 test/unit/state/state_with_name_test.rb            |   43 +
 test/unit/state/state_with_namespace_test.rb       |   22 +
 test/unit/state/state_with_nil_value_test.rb       |   35 +
 .../state_with_redefined_context_method_test.rb    |   45 +
 test/unit/state/state_with_symbolic_value_test.rb  |   32 +
 ...inherited_method_call_for_current_state_test.rb |   40 +
 ...ith_valid_method_call_for_current_state_test.rb |   33 +
 ...h_valid_method_call_for_different_state_test.rb |   41 +
 .../state_without_cached_lambda_value_test.rb      |   25 +
 test/unit/state/state_without_name_test.rb         |   39 +
 .../state_collection_by_default_test.rb            |   21 +
 .../state_collection_string_test.rb                |   35 +
 .../unit/state_collection/state_collection_test.rb |   74 +
 ...ate_collection_with_custom_state_values_test.rb |   29 +
 ...state_collection_with_event_transitions_test.rb |   39 +
 .../state_collection_with_initial_state_test.rb    |   40 +
 .../state_collection_with_namespace_test.rb        |   21 +
 .../state_collection_with_state_behaviors_test.rb  |   40 +
 .../state_collection_with_state_matchers_test.rb   |   29 +
 ...te_collection_with_transition_callbacks_test.rb |   40 +
 .../unit/state_context/state_context_proxy_test.rb |   26 +
 ...ext_proxy_with_if_and_unless_conditions_test.rb |   42 +
 .../state_context_proxy_with_if_condition_test.rb  |   64 +
 ...ntext_proxy_with_multiple_if_conditions_test.rb |   32 +
 ...t_proxy_with_multiple_unless_conditions_test.rb |   32 +
 ...ate_context_proxy_with_unless_condition_test.rb |   64 +
 .../state_context_proxy_without_conditions_test.rb |   31 +
 test/unit/state_context/state_context_test.rb      |   28 +
 .../state_context/state_context_transition_test.rb |  104 +
 .../state_context_with_matching_transition_test.rb |   27 +
 .../state_machine/state_machine_by_default_test.rb |   12 +
 test/unit/state_machine/state_machine_test.rb      |   20 +
 .../transition_after_being_performed_test.rb       |   48 +
 .../transition_after_being_persisted_test.rb       |   46 +
 .../transition_after_being_rolled_back_test.rb     |   35 +
 test/unit/transition/transition_equality_test.rb   |   52 +
 test/unit/transition/transition_loopback_test.rb   |   18 +
 test/unit/transition/transition_test.rb            |   96 +
 test/unit/transition/transition_transient_test.rb  |   20 +
 .../unit/transition/transition_with_action_test.rb |   27 +
 ...transition_with_after_callbacks_skipped_test.rb |  127 ++
 .../transition_with_after_callbacks_test.rb        |   93 +
 .../transition_with_around_callbacks_test.rb       |  141 ++
 ...ransition_with_before_callbacks_skipped_test.rb |   30 +
 .../transition_with_before_callbacks_test.rb       |  104 +
 ...ransition_with_custom_machine_attribute_test.rb |   28 +
 .../transition_with_different_states_test.rb       |   18 +
 .../transition_with_dynamic_to_value_test.rb       |   19 +
 .../transition_with_failure_callbacks_test.rb      |   84 +
 .../transition_with_invalid_nodes_test.rb          |   29 +
 .../transition_with_mixed_callbacks_test.rb        |  105 +
 ...ransition_with_multiple_after_callbacks_test.rb |   40 +
 ...ansition_with_multiple_around_callbacks_test.rb |  114 +
 ...ansition_with_multiple_before_callbacks_test.rb |   40 +
 ...nsition_with_multiple_failure_callbacks_test.rb |   40 +
 .../transition/transition_with_namespace_test.rb   |   47 +
 .../transition_with_perform_arguments_test.rb      |   35 +
 .../transition_with_transactions_test.rb           |   42 +
 .../transition_without_callbacks_test.rb           |   33 +
 .../transition_without_reading_state_test.rb       |   22 +
 .../transition_without_running_action_test.rb      |   47 +
 ...ribute_transition_collection_by_default_test.rb |   23 +
 ...ibute_transition_collection_marshalling_test.rb |   64 +
 ...transition_collection_with_action_error_test.rb |   44 +
 ...ransition_collection_with_action_failed_test.rb |   44 +
 ...on_collection_with_after_callback_error_test.rb |   32 +
 ...ion_collection_with_after_callback_halt_test.rb |   33 +
 ..._with_around_after_yield_callback_error_test.rb |   32 +
 ..._with_around_callback_after_yield_error_test.rb |   32 +
 ...n_with_around_callback_after_yield_halt_test.rb |   33 +
 ..._with_around_callback_before_yield_halt_test.rb |   33 +
 ...n_collection_with_before_callback_error_test.rb |   32 +
 ...on_collection_with_before_callback_halt_test.rb |   33 +
 ...te_transition_collection_with_callbacks_test.rb |   68 +
 ...ition_collection_with_event_transitions_test.rb |   41 +
 ...ibute_transition_collection_with_events_test.rb |   44 +
 ...collection_with_skipped_after_callbacks_test.rb |   42 +
 .../transition_collection_by_default_test.rb       |   23 +
 .../transition_collection_empty_with_block_test.rb |   23 +
 ...ansition_collection_empty_without_block_test.rb |   12 +
 .../transition_collection_invalid_test.rb          |   21 +
 .../transition_collection_partial_invalid_test.rb  |   69 +
 .../transition_collection_test.rb                  |   26 +
 .../transition_collection_valid_test.rb            |   57 +
 ...transition_collection_with_action_error_test.rb |   66 +
 ...ransition_collection_with_action_failed_test.rb |   60 +
 ...n_collection_with_action_hook_and_block_test.rb |   17 +
 ...ion_with_action_hook_and_skipped_action_test.rb |   17 +
 ...action_hook_and_skipped_after_callbacks_test.rb |   37 +
 ...sition_collection_with_action_hook_base_test.rb |   34 +
 ...ition_collection_with_action_hook_error_test.rb |   29 +
 ...ion_collection_with_action_hook_invalid_test.rb |   17 +
 ...on_collection_with_action_hook_multiple_test.rb |   79 +
 .../transition_collection_with_action_hook_test.rb |   45 +
 ...with_action_hook_with_different_actions_test.rb |   48 +
 ...ection_with_action_hook_with_nil_action_test.rb |   42 +
 ...ion_collection_with_after_callback_halt_test.rb |   51 +
 ...on_collection_with_before_callback_halt_test.rb |   47 +
 .../transition_collection_with_block_test.rb       |   46 +
 .../transition_collection_with_callbacks_test.rb   |  135 ++
 ...ition_collection_with_different_actions_test.rb |  189 ++
 ...ition_collection_with_duplicate_actions_test.rb |   48 +
 ...ransition_collection_with_empty_actions_test.rb |   41 +
 ...ransition_collection_with_mixed_actions_test.rb |   41 +
 ...llection_with_skipped_actions_and_block_test.rb |   34 +
 ...nsition_collection_with_skipped_actions_test.rb |   69 +
 ...ed_after_callbacks_and_around_callbacks_test.rb |   53 +
 ...collection_with_skipped_after_callbacks_test.rb |   34 +
 ...transition_collection_with_transactions_test.rb |   65 +
 ...nsition_collection_without_transactions_test.rb |   29 +
 459 files changed, 23653 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..27474cf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+tmp
+*.bundle
+*.so
+*.o
+*.a
+mkmf.log
+.idea/
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..0d786ba
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,3 @@
+--color
+--warnings
+--require spec_helper
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b24e118
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: ruby
+sudo: false
+script: "bundle exec rake"
+cache: bundler
+rvm:
+  - 2.1
+  - 2.0.0
+  - 2.2
+  - jruby
+  - rbx-2
+matrix:
+  allow_failures:
+    - rvm: rbx-2
+    - rvm: jruby
diff --git a/Changelog.md b/Changelog.md
new file mode 100644
index 0000000..7f54fa6
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,16 @@
+*   Fixed inconsistent use of :use_transactions 
+
+*   Namespaced integrations are not registered by default anymore
+
+*   Pass `static: false` in case you don't want initial states to be forced. e.g.
+
+    ```ruby
+    # will set the initial machine state
+    @machines.initialize_states(@object)
+
+    # optionally you can pass the attributes to have that as the initial state
+    @machines.initialize_states(@object, {}, { state: 'finished' })
+
+    # or pass set `static` to false if you want to keep the `object.state` current value
+    @machines.initialize_states(@object, { static: false })
+    ```
diff --git a/Contributors.md b/Contributors.md
new file mode 100644
index 0000000..b84d93e
--- /dev/null
+++ b/Contributors.md
@@ -0,0 +1,39 @@
+- Aaron Gibralter
+- Aaron Pfeifer
+- Abdelkader Boudih
+- Akira Matsuda
+- Andrea Longhi
+- Brad Heller
+- Brandon Dimcheff
+- Casey Howard
+- Chinasaur
+- Daniel Huckstep
+- Durran Jordan
+- Gareth Adams
+- Jahangir Zinedine
+- Jeremy Wells
+- Joe Lind
+- Jon Evans
+- Markus Schirp
+- Michael Klishin
+- Mikhail Shirkov
+- Mohamed Alouane
+- Nate Murray
+- Nathan Long
+- Nicolas Blanco
+- Pawel Pierzchala
+- Pete Forde
+- Peter Lampesberger
+- Rin Raeuber
+- Robert Poor
+- Rustam Zagirov
+- Sandro Turriate and Tim Pope
+- Sean O'Brien
+- Stefan Penner
+- Steve Richert
+- Wojciech Wnętrzak
+- @chris
+- @gmitrev
+- @nblumoe
+- @reiner
+- @sanemat
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..497b9a6
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,8 @@
+source 'https://rubygems.org'
+gemspec
+
+platform :mri_20, :mri_21 do
+  gem 'pry-byebug'
+end
+
+gem 'minitest-reporters'
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..a671699
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,23 @@
+Copyright (c) 2006-2012 Aaron Pfeifer
+Copyright (c) 2014-2015 Abdelkader Boudih
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8875009
--- /dev/null
+++ b/README.md
@@ -0,0 +1,601 @@
+[![Build Status](https://travis-ci.org/state-machines/state_machines.svg?branch=master)](https://travis-ci.org/state-machines/state_machines)
+[![Code Climate](https://codeclimate.com/github/state-machines/state_machines.png)](https://codeclimate.com/github/state-machines/state_machines)
+# State Machines
+
+State Machines adds support for creating state machines for attributes on any Ruby class.
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+    gem 'state_machines'
+
+And then execute:
+
+    $ bundle
+
+Or install it yourself as:
+
+    $ gem install state_machines
+
+## Usage
+
+### Example
+
+Below is an example of many of the features offered by this plugin, including:
+
+* Initial states
+* Namespaced states
+* Transition callbacks
+* Conditional transitions
+* State-driven instance behavior
+* Customized state values
+* Parallel events
+* Path analysis
+
+Class definition:
+
+```ruby
+class Vehicle
+  attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
+
+  state_machine :state, initial: :parked do
+    before_transition parked: :any - :parked, do: :put_on_seatbelt
+
+    after_transition on: :crash, do: :tow
+    after_transition on: :repair, :do: :fix
+    after_transition any => :parked do |vehicle, transition|
+      vehicle.seatbelt_on = false
+    end
+
+    after_failure on: :ignite, do: :log_start_failure
+
+    around_transition do |vehicle, transition, block|
+      start = Time.now
+      block.call
+      vehicle.time_used += Time.now - start
+    end
+
+    event :park do
+      transition [:idling, :first_gear] => :parked
+    end
+
+    event :ignite do
+      transition stalled: same, parked: :idling
+    end
+
+    event :idle do
+      transition first_gear: :idling
+    end
+
+    event :shift_up do
+      transition idling: :first_gear, first_gear: :second_gear, second_gear: :third_gear
+    end
+
+    event :shift_down do
+      transition third_gear: :second_gear, second_gear: :first_gear
+    end
+
+    event :crash do
+      transition all - [:parked, :stalled] => :stalled, if: ->(vehicle) {!vehicle.passed_inspection?}
+    end
+
+    event :repair do
+      # The first transition that matches the state and passes its conditions
+      # will be used
+      transition stalled: parked, unless: :auto_shop_busy
+      transition stalled: same
+    end
+
+    state :parked do
+      def speed
+        0
+      end
+    end
+
+    state :idling, :first_gear do
+      def speed
+        10
+      end
+    end
+
+    state all - [:parked, :stalled, :idling] do
+      def moving?
+        true
+      end
+    end
+
+    state :parked, :stalled, :idling do
+      def moving?
+        false
+      end
+    end
+  end
+
+  state_machine :alarm_state, initial: :active, namespace: :'alarm' do
+    event :enable do
+      transition all => :active
+    end
+
+    event :disable do
+      transition all => :off
+    end
+
+    state :active, :value => 1
+    state :off, :value => 0
+  end
+
+  def initialize
+    @seatbelt_on = false
+    @time_used = 0
+    @auto_shop_busy = true
+    super() # NOTE: This *must* be called, otherwise states won't get initialized
+  end
+
+  def put_on_seatbelt
+    @seatbelt_on = true
+  end
+
+  def passed_inspection?
+    false
+  end
+
+  def tow
+    # tow the vehicle
+  end
+
+  def fix
+    # get the vehicle fixed by a mechanic
+  end
+
+  def log_start_failure
+    # log a failed attempt to start the vehicle
+  end
+end
+```
+
+**Note** the comment made on the `initialize` method in the class.  In order for
+state machine attributes to be properly initialized, `super()` must be called.
+See `StateMachines:MacroMethods` for more information about this.
+
+Using the above class as an example, you can interact with the state machine
+like so:
+
+```ruby
+vehicle = Vehicle.new           # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
+vehicle.state                   # => "parked"
+vehicle.state_name              # => :parked
+vehicle.human_state_name        # => "parked"
+vehicle.parked?                 # => true
+vehicle.can_ignite?             # => true
+vehicle.ignite_transition       # => #<StateMachines:Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
+vehicle.state_events            # => [:ignite]
+vehicle.state_transitions       # => [#<StateMachines:Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
+vehicle.speed                   # => 0
+vehicle.moving?                 # => false
+
+vehicle.ignite                  # => true
+vehicle.parked?                 # => false
+vehicle.idling?                 # => true
+vehicle.speed                   # => 10
+vehicle                         # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>
+
+vehicle.shift_up                # => true
+vehicle.speed                   # => 10
+vehicle.moving?                 # => true
+vehicle                         # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>
+
+# A generic event helper is available to fire without going through the event's instance method
+vehicle.fire_state_event(:shift_up) # => true
+
+# Call state-driven behavior that's undefined for the state raises a NoMethodError
+vehicle.speed                   # => NoMethodError: super: no superclass method `speed' for #<Vehicle:0xb7cf4eac>
+vehicle                         # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>
+
+# The bang (!) operator can raise exceptions if the event fails
+vehicle.park!                   # => StateMachines:InvalidTransition: Cannot transition state via :park from :second_gear
+
+# Generic state predicates can raise exceptions if the value does not exist
+vehicle.state?(:parked)         # => false
+vehicle.state?(:invalid)        # => IndexError: :invalid is an invalid name
+
+# Namespaced machines have uniquely-generated methods
+vehicle.alarm_state             # => 1
+vehicle.alarm_state_name        # => :active
+
+vehicle.can_disable_alarm?      # => true
+vehicle.disable_alarm           # => true
+vehicle.alarm_state             # => 0
+vehicle.alarm_state_name        # => :off
+vehicle.can_enable_alarm?       # => true
+
+vehicle.alarm_off?              # => true
+vehicle.alarm_active?           # => false
+
+# Events can be fired in parallel
+vehicle.fire_events(:shift_down, :enable_alarm) # => true
+vehicle.state_name                              # => :first_gear
+vehicle.alarm_state_name                        # => :active
+
+vehicle.fire_events!(:ignite, :enable_alarm)    # => StateMachines:InvalidTransition: Cannot run events in parallel: ignite, enable_alarm
+
+# Human-friendly names can be accessed for states/events
+Vehicle.human_state_name(:first_gear)               # => "first gear"
+Vehicle.human_alarm_state_name(:active)             # => "active"
+
+Vehicle.human_state_event_name(:shift_down)         # => "shift down"
+Vehicle.human_alarm_state_event_name(:enable)       # => "enable"
+
+# States / events can also be references by the string version of their name
+Vehicle.human_state_name('first_gear')              # => "first gear"
+Vehicle.human_state_event_name('shift_down')        # => "shift down"
+
+# Available transition paths can be analyzed for an object
+vehicle.state_paths                                       # => [[#<StateMachines:Transition ...], [#<StateMachines:Transition ...], ...]
+vehicle.state_paths.to_states                             # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]
+vehicle.state_paths.events                                # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]
+
+# Find all paths that start and end on certain states
+vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[
+                                                          #       #<StateMachines:Transition attribute=:state event=:ignite from="parked" ...>,
+                                                          #       #<StateMachines:Transition attribute=:state event=:shift_up from="idling" ...>
+                                                          #    ]]
+# Skipping state_machine and writing to attributes directly
+vehicle.state = "parked"
+vehicle.state                   # => "parked"
+vehicle.state_name              # => :parked
+
+# *Note* that the following is not supported (see StateMachines:MacroMethods#state_machine):
+# vehicle.state = :parked
+```
+
+## Additional Topics
+
+### Explicit vs. Implicit Event Transitions
+
+Every event defined for a state machine generates an instance method on the
+class that allows the event to be explicitly triggered.  Most of the examples in
+the state_machine documentation use this technique.  However, with some types of
+integrations, like ActiveRecord, you can also *implicitly* fire events by
+setting a special attribute on the instance.
+
+Suppose you're using the ActiveRecord integration and the following model is
+defined:
+
+```ruby
+class Vehicle < ActiveRecord::Base
+  state_machine initial: :parked do
+    event :ignite do
+      transition parked: :idling
+    end
+  end
+end
+```
+
+To trigger the `ignite` event, you would typically call the `Vehicle#ignite`
+method like so:
+
+```ruby
+vehicle = Vehicle.create    # => #<Vehicle id=1 state="parked">
+vehicle.ignite              # => true
+vehicle.state               # => "idling"
+```
+
+This is referred to as an *explicit* event transition.  The same behavior can
+also be achieved *implicitly* by setting the state event attribute and invoking
+the action associated with the state machine.  For example:
+
+```ruby
+vehicle = Vehicle.create        # => #<Vehicle id=1 state="parked">
+vehicle.state_event = 'ignite'  # => 'ignite'
+vehicle.save                    # => true
+vehicle.state                   # => 'idling'
+vehicle.state_event             # => nil
+```
+
+As you can see, the `ignite` event was automatically triggered when the `save`
+action was called.  This is particularly useful if you want to allow users to
+drive the state transitions from a web API.
+
+See each integration's API documentation for more information on the implicit
+approach.
+
+### Symbols vs. Strings
+
+In all of the examples used throughout the documentation, you'll notice that
+states and events are almost always referenced as symbols.  This isn't a
+requirement, but rather a suggested best practice.
+
+You can very well define your state machine with Strings like so:
+
+```ruby
+class Vehicle
+  state_machine initial: 'parked' do
+    event 'ignite' do
+      transition 'parked' => 'idling'
+    end
+
+    # ...
+  end
+end
+```
+
+You could even use numbers as your state / event names.  The **important** thing
+to keep in mind is that the type being used for referencing states / events in
+your machine definition must be **consistent**.  If you're using Symbols, then
+all states / events must use Symbols.  Otherwise you'll encounter the following
+error:
+
+```ruby
+class Vehicle
+  state_machine do
+    event :ignite do
+      transition parked: 'idling'
+    end
+  end
+end
+
+# => ArgumentError: "idling" state defined as String, :parked defined as Symbol; all states must be consistent
+```
+
+There **is** an exception to this rule.  The consistency is only required within
+the definition itself.  However, when the machine's helper methods are called
+with input from external sources, such as a web form, state_machine will map
+that input to a String / Symbol.  For example:
+
+```ruby
+class Vehicle
+  state_machine initial: :parked do
+    event :ignite do
+      transition parked: :idling
+    end
+  end
+end
+
+v = Vehicle.new     # => #<Vehicle:0xb71da5f8 @state="parked">
+v.state?('parked')  # => true
+v.state?(:parked)   # => true
+```
+
+**Note** that none of this actually has to do with the type of the value that
+gets stored.  By default, all state values are assumed to be string -- regardless
+of whether the state names are symbols or strings.  If you want to store states
+as symbols instead you'll have to be explicit about it:
+
+```ruby
+class Vehicle
+  state_machine initial: :parked do
+    event :ignite do
+      transition parked: :idling
+    end
+
+    states.each do |state|
+      self.state(state.name, :value => state.name.to_sym)
+    end
+  end
+end
+
+v = Vehicle.new     # => #<Vehicle:0xb71da5f8 @state=:parked>
+v.state?('parked')  # => true
+v.state?(:parked)   # => true
+```
+
+### Syntax flexibility
+
+Although state_machine introduces a simplified syntax, it still remains
+backwards compatible with previous versions and other state-related libraries by
+providing some flexibility around how transitions are defined.  See below for an
+overview of these syntaxes.
+
+#### Verbose syntax
+
+In general, it's recommended that state machines use the implicit syntax for
+transitions.  However, you can be a little more explicit and verbose about
+transitions by using the `:from`, `:except_from`, `:to`,
+and `:except_to` options.
+
+For example, transitions and callbacks can be defined like so:
+
+```ruby
+class Vehicle
+  state_machine initial: :parked do
+    before_transition from: :parked, except_to: :parked, do: :put_on_seatbelt
+    after_transition to: :parked do |transition|
+      self.seatbelt = 'off' # self is the record
+    end
+
+    event :ignite do
+      transition from: :parked, to: :idling
+    end
+  end
+end
+```
+
+#### Transition context
+
+Some flexibility is provided around the context in which transitions can be
+defined.  In almost all examples throughout the documentation, transitions are
+defined within the context of an event.  If you prefer to have state machines
+defined in the context of a **state** either out of preference or in order to
+easily migrate from a different library, you can do so as shown below:
+
+```ruby
+class Vehicle
+  state_machine initial: :parked do
+    ...
+
+    state :parked do
+      transition to::idling, :on => [:ignite, :shift_up], if: :seatbelt_on?
+
+      def speed
+        0
+      end
+    end
+
+    state :first_gear do
+      transition to: :second_gear, on: :shift_up
+
+      def speed
+        10
+      end
+    end
+
+    state :idling, :first_gear do
+      transition to: :parked, on: :park
+    end
+  end
+end
+```
+
+In the above example, there's no need to specify the `from` state for each
+transition since it's inferred from the context.
+
+You can also define transitions completely outside the context of a particular
+state / event.  This may be useful in cases where you're building a state
+machine from a data store instead of part of the class definition.  See the
+example below:
+
+```ruby
+class Vehicle
+  state_machine initial: :parked do
+    ...
+
+    transition parked: :idling, :on => [:ignite, :shift_up]
+    transition first_gear: :second_gear, second_gear: :third_gear, on: :shift_up
+    transition [:idling, :first_gear] => :parked, on: :park
+    transition [:idling, :first_gear] => :parked, on: :park
+    transition all - [:parked, :stalled]: :stalled, unless: :auto_shop_busy?
+  end
+end
+```
+
+Notice that in these alternative syntaxes:
+
+* You can continue to configure `:if` and `:unless` conditions
+* You can continue to define `from` states (when in the machine context) using
+the `all`, `any`, and `same` helper methods
+
+### Static / Dynamic definitions
+
+In most cases, the definition of a state machine is **static**.  That is to say,
+the states, events and possible transitions are known ahead of time even though
+they may depend on data that's only known at runtime.  For example, certain
+transitions may only be available depending on an attribute on that object it's
+being run on.  All of the documentation in this library define static machines
+like so:
+
+```ruby
+class Vehicle
+  state_machine :state, initial: :parked do
+    event :park do
+      transition [:idling, :first_gear] => :parked
+    end
+
+    ...
+  end
+end
+```
+
+However, there may be cases where the definition of a state machine is **dynamic**.
+This means that you don't know the possible states or events for a machine until
+runtime.  For example, you may allow users in your application to manage the
+state machine of a project or task in your system.  This means that the list of
+transitions (and their associated states / events) could be stored externally,
+such as in a database.  In a case like this, you can define dynamically-generated
+state machines like so:
+
+```ruby
+class Vehicle
+  attr_accessor :state
+
+  # Make sure the machine gets initialized so the initial state gets set properly
+  def initialize(*)
+    super
+    machine
+  end
+
+  # Replace this with an external source (like a db)
+  def transitions
+    [
+      {parked: :idling, on: :ignite},
+      {idling: :first_gear, first_gear: :second_gear, on: :shift_up}
+      # ...
+    ]
+  end
+
+  # Create a state machine for this vehicle instance dynamically based on the
+  # transitions defined from the source above
+  def machine
+    vehicle = self
+    @machine ||= Machine.new(vehicle, initial: :parked, action: :save) do
+      vehicle.transitions.each {|attrs| transition(attrs)}
+    end
+  end
+
+  def save
+    # Save the state change...
+    true
+  end
+end
+
+# Generic class for building machines
+class Machine
+  def self.new(object, *args, &block)
+    machine_class = Class.new
+    machine = machine_class.state_machine(*args, &block)
+    attribute = machine.attribute
+    action = machine.action
+
+    # Delegate attributes
+    machine_class.class_eval do
+      define_method(:definition) { machine }
+      define_method(attribute) { object.send(attribute) }
+      define_method("#{attribute}=") {|value| object.send("#{attribute}=", value) }
+      define_method(action) { object.send(action) } if action
+    end
+
+    machine_class.new
+  end
+end
+
+vehicle = Vehicle.new                   # => #<Vehicle:0xb708412c @state="parked" ...>
+vehicle.state                           # => "parked"
+vehicle.machine.ignite                  # => true
+vehicle.machine.state                   # => "idling
+vehicle.state                           # => "idling"
+vehicle.machine.state_transitions       # => [#<StateMachines:Transition ...>]
+vehicle.machine.definition.states.keys  # => :first_gear, :second_gear, :parked, :idling
+```
+
+As you can see, state_machine provides enough flexibility for you to be able
+to create new machine definitions on the fly based on an external source of
+transitions.
+
+## Dependencies
+
+Ruby versions officially supported and tested:
+
+* Ruby (MRI) 2.0.0+
+* JRuby
+* Rubinius
+
+For graphing state machine:
+
+* [state_machines-graphviz](http://github.com/state-machines/state_machines-graphviz)
+
+For documenting state machines:
+
+* [state_machines-yard](http://github.com/state-machines/state_machines-yard)
+
+
+## TODO
+
+* Add matchers/assertions for rspec and minitest
+
+## Contributing
+
+1. Fork it ( https://github.com/state-machines/state_machines/fork )
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create a new Pull Request
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..85e08ab
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,12 @@
+require 'bundler/gem_tasks'
+require 'rake/testtask'
+Rake::TestTask.new(:functional) do |t|
+  t.test_files = FileList['test/functional/*_test.rb']
+end
+
+Rake::TestTask.new(:unit) do |t|
+  t.test_files = FileList['test/unit/**/*_test.rb']
+end
+
+desc 'Default: run all tests.'
+task default: [:unit, :functional]
\ No newline at end of file
diff --git a/Testing.md b/Testing.md
new file mode 100644
index 0000000..e69de29
diff --git a/lib/state_machines.rb b/lib/state_machines.rb
new file mode 100644
index 0000000..2fcff97
--- /dev/null
+++ b/lib/state_machines.rb
@@ -0,0 +1,3 @@
+require 'state_machines/version'
+require 'state_machines/core'
+require 'state_machines/core_ext'
\ No newline at end of file
diff --git a/lib/state_machines/assertions.rb b/lib/state_machines/assertions.rb
new file mode 100644
index 0000000..044a550
--- /dev/null
+++ b/lib/state_machines/assertions.rb
@@ -0,0 +1,40 @@
+class Hash
+  # Provides a set of helper methods for making assertions about the content
+  # of various objects
+
+  unless respond_to?(:assert_valid_keys)
+    # Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
+    # on a mismatch. Note that keys are NOT treated indifferently, meaning if you
+    # use strings for keys but assert symbols as keys, this will fail.
+    #
+    #   { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
+    #   { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
+    #   { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age)   # => passes, raises nothing
+    # Code from ActiveSupport
+    def assert_valid_keys(*valid_keys)
+      valid_keys.flatten!
+      each_key do |k|
+        unless valid_keys.include?(k)
+          raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
+        end
+      end
+    end
+  end
+
+  # Validates that the given hash only includes at *most* one of a set of
+  # exclusive keys.  If more than one key is found, an ArgumentError will be
+  # raised.
+  #
+  # == Examples
+  #
+  #   options = {:only => :on, :except => :off}
+  #   options.assert_exclusive_keys(:only)                   # => nil
+  #   options.assert_exclusive_keys(:except)                 # => nil
+  #   options.assert_exclusive_keys(:only, :except)          # => ArgumentError: Conflicting keys: only, except
+  #   options.assert_exclusive_keys(:only, :except, :with)   # => ArgumentError: Conflicting keys: only, except
+  def assert_exclusive_keys(*exclusive_keys)
+    conflicting_keys = exclusive_keys & keys
+    raise ArgumentError, "Conflicting keys: #{conflicting_keys.join(', ')}" unless conflicting_keys.length <= 1
+  end
+end
+
diff --git a/lib/state_machines/branch.rb b/lib/state_machines/branch.rb
new file mode 100644
index 0000000..1ab4eb6
--- /dev/null
+++ b/lib/state_machines/branch.rb
@@ -0,0 +1,183 @@
+module StateMachines
+  # Represents a set of requirements that must be met in order for a transition
+  # or callback to occur.  Branches verify that the event, from state, and to
+  # state of the transition match, in addition to if/unless conditionals for
+  # an object's state.
+  class Branch
+
+    include EvalHelpers
+    
+    # The condition that must be met on an object
+    attr_reader :if_condition
+    
+    # The condition that must *not* be met on an object
+    attr_reader :unless_condition
+    
+    # The requirement for verifying the event being matched
+    attr_reader :event_requirement
+    
+    # One or more requirements for verifying the states being matched.  All
+    # requirements contain a mapping of {:from => matcher, :to => matcher}.
+    attr_reader :state_requirements
+    
+    # A list of all of the states known to this branch.  This will pull states
+    # from the following options (in the same order):
+    # * +from+ / +except_from+
+    # * +to+ / +except_to+
+    attr_reader :known_states
+    
+    # Creates a new branch
+    def initialize(options = {}) #:nodoc:
+      # Build conditionals
+      @if_condition = options.delete(:if)
+      @unless_condition = options.delete(:unless)
+      
+      # Build event requirement
+      @event_requirement = build_matcher(options, :on, :except_on)
+      
+      if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on]).empty?
+        # Explicit from/to requirements specified
+        @state_requirements = [{:from => build_matcher(options, :from, :except_from), :to => build_matcher(options, :to, :except_to)}]
+      else
+        # Separate out the event requirement
+        options.delete(:on)
+        options.delete(:except_on)
+        
+        # Implicit from/to requirements specified
+        @state_requirements = options.collect do |from, to|
+          from = WhitelistMatcher.new(from) unless from.is_a?(Matcher)
+          to = WhitelistMatcher.new(to) unless to.is_a?(Matcher)
+          {:from => from, :to => to}
+        end
+      end
+      
+      # Track known states.  The order that requirements are iterated is based
+      # on the priority in which tracked states should be added.
+      @known_states = []
+      @state_requirements.each do |state_requirement|
+        [:from, :to].each {|option| @known_states |= state_requirement[option].values}
+      end
+    end
+    
+    # Determines whether the given object / query matches the requirements
+    # configured for this branch.  In addition to matching the event, from state,
+    # and to state, this will also check whether the configured :if/:unless
+    # conditions pass on the given object.
+    # 
+    # == Examples
+    # 
+    #   branch = StateMachines::Branch.new(:parked => :idling, :on => :ignite)
+    #   
+    #   # Successful
+    #   branch.matches?(object, :on => :ignite)                                   # => true
+    #   branch.matches?(object, :from => nil)                                     # => true
+    #   branch.matches?(object, :from => :parked)                                 # => true
+    #   branch.matches?(object, :to => :idling)                                   # => true
+    #   branch.matches?(object, :from => :parked, :to => :idling)                 # => true
+    #   branch.matches?(object, :on => :ignite, :from => :parked, :to => :idling) # => true
+    #   
+    #   # Unsuccessful
+    #   branch.matches?(object, :on => :park)                                     # => false
+    #   branch.matches?(object, :from => :idling)                                 # => false
+    #   branch.matches?(object, :to => :first_gear)                               # => false
+    #   branch.matches?(object, :from => :parked, :to => :first_gear)             # => false
+    #   branch.matches?(object, :on => :park, :from => :parked, :to => :idling)   # => false
+    def matches?(object, query = {})
+      !match(object, query).nil?
+    end
+    
+    # Attempts to match the given object / query against the set of requirements
+    # configured for this branch.  In addition to matching the event, from state,
+    # and to state, this will also check whether the configured :if/:unless
+    # conditions pass on the given object.
+    # 
+    # If a match is found, then the event/state requirements that the query
+    # passed successfully will be returned.  Otherwise, nil is returned if there
+    # was no match.
+    # 
+    # Query options:
+    # * <tt>:from</tt> - One or more states being transitioned from.  If none
+    #   are specified, then this will always match.
+    # * <tt>:to</tt> - One or more states being transitioned to.  If none are
+    #   specified, then this will always match.
+    # * <tt>:on</tt> - One or more events that fired the transition.  If none
+    #   are specified, then this will always match.
+    # * <tt>:guard</tt> - Whether to guard matches with the if/unless
+    #   conditionals defined for this branch.  Default is true.
+    # 
+    # == Examples
+    # 
+    #   branch = StateMachines::Branch.new(:parked => :idling, :on => :ignite)
+    #   
+    #   branch.match(object, :on => :ignite)  # => {:to => ..., :from => ..., :on => ...}
+    #   branch.match(object, :on => :park)    # => nil
+    def match(object, query = {})
+      query.assert_valid_keys(:from, :to, :on, :guard)
+      
+      if (match = match_query(query)) && matches_conditions?(object, query)
+        match
+      end
+    end
+
+    def draw(graph, event, valid_states)
+     fail NotImplementedError
+    end
+    
+    protected
+      # Builds a matcher strategy to use for the given options.  If neither a
+      # whitelist nor a blacklist option is specified, then an AllMatcher is
+      # built.
+      def build_matcher(options, whitelist_option, blacklist_option)
+        options.assert_exclusive_keys(whitelist_option, blacklist_option)
+        
+        if options.include?(whitelist_option)
+          value = options[whitelist_option]
+          value.is_a?(Matcher) ? value : WhitelistMatcher.new(options[whitelist_option])
+        elsif options.include?(blacklist_option)
+          value = options[blacklist_option]
+          raise ArgumentError, ":#{blacklist_option} option cannot use matchers; use :#{whitelist_option} instead" if value.is_a?(Matcher)
+          BlacklistMatcher.new(value)
+        else
+          AllMatcher.instance
+        end
+      end
+      
+      # Verifies that all configured requirements (event and state) match the
+      # given query.  If a match is found, then a hash containing the
+      # event/state requirements that passed will be returned; otherwise, nil.
+      def match_query(query)
+        query ||= {}
+        
+        if match_event(query) && (state_requirement = match_states(query))
+          state_requirement.merge(:on => event_requirement)
+        end
+      end
+      
+      # Verifies that the event requirement matches the given query
+      def match_event(query)
+        matches_requirement?(query, :on, event_requirement)
+      end
+      
+      # Verifies that the state requirements match the given query.  If a
+      # matching requirement is found, then it is returned.
+      def match_states(query)
+        state_requirements.detect do |state_requirement|
+          [:from, :to].all? {|option| matches_requirement?(query, option, state_requirement[option])}
+        end
+      end
+      
+      # Verifies that an option in the given query matches the values required
+      # for that option
+      def matches_requirement?(query, option, requirement)
+        !query.include?(option) || requirement.matches?(query[option], query)
+      end
+      
+      # Verifies that the conditionals for this branch evaluate to true for the
+      # given object
+      def matches_conditions?(object, query)
+        query[:guard] == false ||
+        Array(if_condition).all? {|condition| evaluate_method(object, condition)} &&
+        !Array(unless_condition).any? {|condition| evaluate_method(object, condition)}
+      end
+  end
+end
diff --git a/lib/state_machines/callback.rb b/lib/state_machines/callback.rb
new file mode 100644
index 0000000..baefc62
--- /dev/null
+++ b/lib/state_machines/callback.rb
@@ -0,0 +1,220 @@
+require 'state_machines/branch'
+require 'state_machines/eval_helpers'
+
+module StateMachines
+  # Callbacks represent hooks into objects that allow logic to be triggered
+  # before, after, or around a specific set of transitions.
+  class Callback
+    include EvalHelpers
+
+    class << self
+      # Determines whether to automatically bind the callback to the object
+      # being transitioned.  This only applies to callbacks that are defined as
+      # lambda blocks (or Procs).  Some integrations, such as DataMapper, handle
+      # callbacks by executing them bound to the object involved, while other
+      # integrations, such as ActiveRecord, pass the object as an argument to
+      # the callback.  This can be configured on an application-wide basis by
+      # setting this configuration to +true+ or +false+.  The default value
+      # is +false+.
+      # 
+      # *Note* that the DataMapper and Sequel integrations automatically
+      # configure this value on a per-callback basis, so it does not have to
+      # be enabled application-wide.
+      # 
+      # == Examples
+      # 
+      # When not bound to the object:
+      # 
+      #   class Vehicle
+      #     state_machine do
+      #       before_transition do |vehicle|
+      #         vehicle.set_alarm
+      #       end
+      #     end
+      #     
+      #     def set_alarm
+      #       ...
+      #     end
+      #   end
+      # 
+      # When bound to the object:
+      # 
+      #   StateMachines::Callback.bind_to_object = true
+      #   
+      #   class Vehicle
+      #     state_machine do
+      #       before_transition do
+      #         self.set_alarm
+      #       end
+      #     end
+      #     
+      #     def set_alarm
+      #       ...
+      #     end
+      #   end
+      attr_accessor :bind_to_object
+
+      # The application-wide terminator to use for callbacks when not
+      # explicitly defined.  Terminators determine whether to cancel a
+      # callback chain based on the return value of the callback.
+      # 
+      # See StateMachines::Callback#terminator for more information.
+      attr_accessor :terminator
+    end
+
+    # The type of callback chain this callback is for.  This can be one of the
+    # following:
+    # * +before+
+    # * +after+
+    # * +around+
+    # * +failure+
+    attr_accessor :type
+
+    # An optional block for determining whether to cancel the callback chain
+    # based on the return value of the callback.  By default, the callback
+    # chain never cancels based on the return value (i.e. there is no implicit
+    # terminator).  Certain integrations, such as ActiveRecord and Sequel,
+    # change this default value.
+    # 
+    # == Examples
+    # 
+    # Canceling the callback chain without a terminator:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       before_transition do |vehicle|
+    #         throw :halt
+    #       end
+    #     end
+    #   end
+    # 
+    # Canceling the callback chain with a terminator value of +false+:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       before_transition do |vehicle|
+    #         false
+    #       end
+    #     end
+    #   end
+    attr_reader :terminator
+
+    # The branch that determines whether or not this callback can be invoked
+    # based on the context of the transition.  The event, from state, and
+    # to state must all match in order for the branch to pass.
+    # 
+    # See StateMachines::Branch for more information.
+    attr_reader :branch
+
+    # Creates a new callback that can get called based on the configured
+    # options.
+    # 
+    # In addition to the possible configuration options for branches, the
+    # following options can be configured:
+    # * <tt>:bind_to_object</tt> - Whether to bind the callback to the object involved.
+    #   If set to false, the object will be passed as a parameter instead.
+    #   Default is integration-specific or set to the application default.
+    # * <tt>:terminator</tt> - A block/proc that determines what callback
+    #   results should cause the callback chain to halt (if not using the
+    #   default <tt>throw :halt</tt> technique).
+    # 
+    # More information about how those options affect the behavior of the
+    # callback can be found in their attribute definitions.
+    def initialize(type, *args, &block)
+      @type = type
+      raise ArgumentError, 'Type must be :before, :after, :around, or :failure' unless [:before, :after, :around, :failure].include?(type)
+
+      options = args.last.is_a?(Hash) ? args.pop : {}
+      @methods = args
+      @methods.concat(Array(options.delete(:do)))
+      @methods << block if block_given?
+      raise ArgumentError, 'Method(s) for callback must be specified' unless @methods.any?
+
+      options = {:bind_to_object => self.class.bind_to_object, :terminator => self.class.terminator}.merge(options)
+
+      # Proxy lambda blocks so that they're bound to the object
+      bind_to_object = options.delete(:bind_to_object)
+      @methods.map! do |method|
+        bind_to_object && method.is_a?(Proc) ? bound_method(method) : method
+      end
+
+      @terminator = options.delete(:terminator)
+      @branch = Branch.new(options)
+    end
+
+    # Gets a list of the states known to this callback by looking at the
+    # branch's known states
+    def known_states
+      branch.known_states
+    end
+
+    # Runs the callback as long as the transition context matches the branch
+    # requirements configured for this callback.  If a block is provided, it
+    # will be called when the last method has run.
+    # 
+    # If a terminator has been configured and it matches the result from the
+    # evaluated method, then the callback chain should be halted.
+    def call(object, context = {}, *args, &block)
+      if @branch.matches?(object, context)
+        run_methods(object, context, 0, *args, &block)
+        true
+      else
+        false
+      end
+    end
+
+    private
+    # Runs all of the methods configured for this callback.
+    #
+    # When running +around+ callbacks, this will evaluate each method and
+    # yield when the last method has yielded.  The callback will only halt if
+    # one of the methods does not yield.
+    #
+    # For all other types of callbacks, this will evaluate each method in
+    # order.  The callback will only halt if the resulting value from the
+    # method passes the terminator.
+    def run_methods(object, context = {}, index = 0, *args, &block)
+      if type == :around
+        current_method = @methods[index]
+        if  current_method
+          yielded = false
+          evaluate_method(object, current_method, *args) do
+            yielded = true
+            run_methods(object, context, index + 1, *args, &block)
+          end
+
+          throw :halt unless yielded
+        else
+          yield if block_given?
+        end
+      else
+        @methods.each do |method|
+          result = evaluate_method(object, method, *args)
+          throw :halt if @terminator && @terminator.call(result)
+        end
+      end
+    end
+
+    # Generates a method that can be bound to the object being transitioned
+    # when the callback is invoked
+    def bound_method(block)
+      type = self.type
+      arity = block.arity
+      arity += 1 if arity >= 0 # Make sure the object gets passed
+      arity += 1 if arity == 1 && type == :around # Make sure the block gets passed
+
+      method = lambda { |object, *args| object.instance_exec(*args, &block) }
+
+
+      # Proxy arity to the original block
+      (
+      class << method;
+        self;
+      end).class_eval do
+        define_method(:arity) { arity }
+      end
+
+      method
+    end
+  end
+end
diff --git a/lib/state_machines/core.rb b/lib/state_machines/core.rb
new file mode 100644
index 0000000..1bca6a1
--- /dev/null
+++ b/lib/state_machines/core.rb
@@ -0,0 +1,43 @@
+# Load all of the core implementation required to use state_machine.  This
+# includes:
+# * StateMachines::MacroMethods which adds the state_machine DSL to your class
+# * A set of initializers for setting state_machine defaults based on the current
+#   running environment (such as within Rails)
+require 'state_machines/assertions'
+require 'state_machines/error'
+
+require 'state_machines/extensions'
+
+require 'state_machines/integrations'
+require 'state_machines/integrations/base'
+
+require 'state_machines/eval_helpers'
+
+require 'singleton'
+require 'state_machines/matcher'
+require 'state_machines/matcher_helpers'
+
+require 'state_machines/transition'
+require 'state_machines/transition_collection'
+
+require 'state_machines/branch'
+
+require 'state_machines/helper_module'
+require 'state_machines/state'
+require 'state_machines/callback'
+require 'state_machines/node_collection'
+
+require 'state_machines/state_context'
+require 'state_machines/state'
+require 'state_machines/state_collection'
+
+require 'state_machines/event'
+require 'state_machines/event_collection'
+
+require 'state_machines/path'
+require 'state_machines/path_collection'
+
+require 'state_machines/machine'
+require 'state_machines/machine_collection'
+
+require 'state_machines/macro_methods'
\ No newline at end of file
diff --git a/lib/state_machines/core_ext.rb b/lib/state_machines/core_ext.rb
new file mode 100644
index 0000000..82a1ffe
--- /dev/null
+++ b/lib/state_machines/core_ext.rb
@@ -0,0 +1,2 @@
+# Loads all of the extensions to be made to Ruby core classes
+require 'state_machines/core_ext/class/state_machine'
diff --git a/lib/state_machines/core_ext/class/state_machine.rb b/lib/state_machines/core_ext/class/state_machine.rb
new file mode 100644
index 0000000..9afb3e7
--- /dev/null
+++ b/lib/state_machines/core_ext/class/state_machine.rb
@@ -0,0 +1,3 @@
+Class.class_eval do
+  include StateMachines::MacroMethods
+end
diff --git a/lib/state_machines/error.rb b/lib/state_machines/error.rb
new file mode 100644
index 0000000..e2c5cb7
--- /dev/null
+++ b/lib/state_machines/error.rb
@@ -0,0 +1,112 @@
+module StateMachines
+  # An error occurred during a state machine invocation
+  class Error < StandardError
+    # The object that failed
+    attr_reader :object
+
+    def initialize(object, message = nil) #:nodoc:
+      @object = object
+
+      super(message)
+    end
+  end
+
+  # An invalid integration was specified
+  class IntegrationNotFound < Error
+    def initialize(name)
+      super(nil, "#{name.inspect} is an invalid integration. #{error_message}")
+    end
+
+    def valid_integrations
+      "Valid integrations are: #{valid_integrations_name}"
+    end
+
+    def valid_integrations_name
+      Integrations.list.collect(&:integration_name)
+    end
+
+    def no_integrations
+      'No integrations registered'
+    end
+
+    def error_message
+      if Integrations.list.size.zero?
+        no_integrations
+      else
+        valid_integrations
+      end
+    end
+  end
+
+  # An invalid integration was registered
+  class IntegrationError < StandardError
+  end
+
+  # An invalid event was specified
+  class InvalidEvent < Error
+    # The event that was attempted to be run
+    attr_reader :event
+
+    def initialize(object, event_name) #:nodoc:
+      @event = event_name
+
+      super(object, "#{event.inspect} is an unknown state machine event")
+    end
+  end
+  # An invalid transition was attempted
+  class InvalidTransition < Error
+    # The machine attempting to be transitioned
+    attr_reader :machine
+
+    # The current state value for the machine
+    attr_reader :from
+
+    def initialize(object, machine, event) #:nodoc:
+      @machine = machine
+      @from_state = machine.states.match!(object)
+      @from = machine.read(object, :state)
+      @event = machine.events.fetch(event)
+      errors = machine.errors_for(object)
+
+      message = "Cannot transition #{machine.name} via :#{self.event} from #{from_name.inspect}"
+      message << " (Reason(s): #{errors})" unless errors.empty?
+      super(object, message)
+    end
+
+    # The event that triggered the failed transition
+    def event
+      @event.name
+    end
+
+    # The fully-qualified name of the event that triggered the failed transition
+    def qualified_event
+      @event.qualified_name
+    end
+
+    # The name for the current state
+    def from_name
+      @from_state.name
+    end
+
+    # The fully-qualified name for the current state
+    def qualified_from_name
+      @from_state.qualified_name
+    end
+  end
+
+  # A set of transition failed to run in parallel
+  class InvalidParallelTransition < Error
+    # The set of events that failed the transition(s)
+    attr_reader :events
+
+    def initialize(object, events) #:nodoc:
+      @events = events
+
+      super(object, "Cannot run events in parallel: #{events * ', '}")
+    end
+  end
+
+  # A method was called in an invalid state context
+  class InvalidContext < Error
+  end
+end
diff --git a/lib/state_machines/eval_helpers.rb b/lib/state_machines/eval_helpers.rb
new file mode 100644
index 0000000..feb0ced
--- /dev/null
+++ b/lib/state_machines/eval_helpers.rb
@@ -0,0 +1,87 @@
+module StateMachines
+  # Provides a set of helper methods for evaluating methods within the context
+  # of an object.
+  module EvalHelpers
+    # Evaluates one of several different types of methods within the context
+    # of the given object.  Methods can be one of the following types:
+    # * Symbol
+    # * Method / Proc
+    # * String
+    # 
+    # == Examples
+    # 
+    # Below are examples of the various ways that a method can be evaluated
+    # on an object:
+    # 
+    #   class Person
+    #     def initialize(name)
+    #       @name = name
+    #     end
+    #     
+    #     def name
+    #       @name
+    #     end
+    #   end
+    #   
+    #   class PersonCallback
+    #     def self.run(person)
+    #       person.name
+    #     end
+    #   end
+    # 
+    #   person = Person.new('John Smith')
+    #   
+    #   evaluate_method(person, :name)                            # => "John Smith"
+    #   evaluate_method(person, PersonCallback.method(:run))      # => "John Smith"
+    #   evaluate_method(person, Proc.new {|person| person.name})  # => "John Smith"
+    #   evaluate_method(person, lambda {|person| person.name})    # => "John Smith"
+    #   evaluate_method(person, '@name')                          # => "John Smith"
+    # 
+    # == Additional arguments
+    # 
+    # Additional arguments can be passed to the methods being evaluated.  If
+    # the method defines additional arguments other than the object context,
+    # then all arguments are required.
+    # 
+    # For example,
+    # 
+    #   person = Person.new('John Smith')
+    #   
+    #   evaluate_method(person, lambda {|person| person.name}, 21)                              # => "John Smith"
+    #   evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21)          # => "John Smith is 21"
+    #   evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21, 'male')  # => ArgumentError: wrong number of arguments (3 for 2)
+    def evaluate_method(object, method, *args, &block)
+      case method
+        when Symbol
+          klass = (class << object; self; end)
+          args = [] if (klass.method_defined?(method) || klass.private_method_defined?(method)) && object.method(method).arity == 0
+          object.send(method, *args, &block)
+        when Proc, Method
+          args.unshift(object)
+          arity = method.arity
+          
+          # Procs don't support blocks in < Ruby 1.9, so it's tacked on as an
+          # argument for consistency across versions of Ruby
+          if block_given? && Proc === method && arity != 0
+            if [1, 2].include?(arity)
+              # Force the block to be either the only argument or the 2nd one
+              # after the object (may mean additional arguments get discarded)
+              args = args[0, arity - 1] + [block]
+            else
+              # Tack the block to the end of the args
+              args << block
+            end
+          else
+            # These method types are only called with 0, 1, or n arguments
+            args = args[0, arity] if [0, 1].include?(arity)
+          end
+          
+          method.is_a?(Proc) ? method.call(*args) : method.call(*args, &block)
+        when String
+          eval(method, object.instance_eval {binding}, &block)
+        else
+          raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated'
+      end
+    end
+  end
+end
diff --git a/lib/state_machines/event.rb b/lib/state_machines/event.rb
new file mode 100644
index 0000000..6f9b27f
--- /dev/null
+++ b/lib/state_machines/event.rb
@@ -0,0 +1,228 @@
+module StateMachines
+  # An event defines an action that transitions an attribute from one state to
+  # another.  The state that an attribute is transitioned to depends on the
+  # branches configured for the event.
+  class Event
+
+    include MatcherHelpers
+
+    # The state machine for which this event is defined
+    attr_accessor :machine
+
+    # The name of the event
+    attr_reader :name
+
+    # The fully-qualified name of the event, scoped by the machine's namespace 
+    attr_reader :qualified_name
+
+    # The human-readable name for the event
+    attr_writer :human_name
+
+    # The list of branches that determine what state this event transitions
+    # objects to when fired
+    attr_reader :branches
+
+    # A list of all of the states known to this event using the configured
+    # branches/transitions as the source
+    attr_reader :known_states
+
+    # Creates a new event within the context of the given machine
+    # 
+    # Configuration options:
+    # * <tt>:human_name</tt> - The human-readable version of this event's name
+    def initialize(machine, name, options = {}) #:nodoc:
+      options.assert_valid_keys(:human_name)
+
+      @machine = machine
+      @name = name
+      @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name
+      @human_name = options[:human_name] || @name.to_s.tr('_', ' ')
+      reset
+
+      # Output a warning if another event has a conflicting qualified name
+      if conflict = machine.owner_class.state_machines.detect { |_other_name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name] }
+        _name, other_machine = conflict
+        warn "Event #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
+      else
+        add_actions
+      end
+    end
+
+    # Creates a copy of this event in addition to the list of associated
+    # branches to prevent conflicts across events within a class hierarchy.
+    def initialize_copy(orig) #:nodoc:
+      super
+      @branches = @branches.dup
+      @known_states = @known_states.dup
+    end
+
+    # Transforms the event name into a more human-readable format, such as
+    # "turn on" instead of "turn_on"
+    def human_name(klass = @machine.owner_class)
+      @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name
+    end
+
+    # Evaluates the given block within the context of this event.  This simply
+    # provides a DSL-like syntax for defining transitions.
+    def context(&block)
+      instance_eval(&block)
+    end
+
+    # Creates a new transition that determines what to change the current state
+    # to when this event fires.
+    # 
+    # Since this transition is being defined within an event context, you do
+    # *not* need to specify the <tt>:on</tt> option for the transition.  For
+    # example:
+    # 
+    #  state_machine do
+    #    event :ignite do
+    #      transition :parked => :idling, :idling => same, :if => :seatbelt_on? # Transitions to :idling if seatbelt is on
+    #      transition all => :parked, :unless => :seatbelt_on?                  # Transitions to :parked if seatbelt is off
+    #    end
+    #  end
+    # 
+    # See StateMachines::Machine#transition for a description of the possible
+    # configurations for defining transitions.
+    def transition(options)
+      raise ArgumentError, 'Must specify as least one transition requirement' if options.empty?
+
+      # Only a certain subset of explicit options are allowed for transition
+      # requirements
+      options.assert_valid_keys(:from, :to, :except_from, :except_to, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty?
+
+      branches << branch = Branch.new(options.merge(:on => name))
+      @known_states |= branch.known_states
+      branch
+    end
+
+    # Determines whether any transitions can be performed for this event based
+    # on the current state of the given object.
+    # 
+    # If the event can't be fired, then this will return false, otherwise true.
+    # 
+    # *Note* that this will not take the object context into account.  Although
+    # a transition may be possible based on the state machine definition,
+    # object-specific behaviors (like validations) may prevent it from firing.
+    def can_fire?(object, requirements = {})
+      !transition_for(object, requirements).nil?
+    end
+
+    # Finds and builds the next transition that can be performed on the given
+    # object.  If no transitions can be made, then this will return nil.
+    # 
+    # Valid requirement options:
+    # * <tt>:from</tt> - One or more states being transitioned from.  If none
+    #   are specified, then this will be the object's current state.
+    # * <tt>:to</tt> - One or more states being transitioned to.  If none are
+    #   specified, then this will match any to state.
+    # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
+    #   conditionals defined for each one.  Default is true.
+    def transition_for(object, requirements = {})
+      requirements.assert_valid_keys(:from, :to, :guard)
+      requirements[:from] = machine.states.match!(object).name unless custom_from_state = requirements.include?(:from)
+
+      branches.each do |branch|
+        if match = branch.match(object, requirements)
+          # Branch allows for the transition to occur
+          from = requirements[:from]
+          to = if match[:to].is_a?(LoopbackMatcher)
+                 from
+               else
+                 values = requirements.include?(:to) ? [requirements[:to]].flatten : [from] | machine.states.map { |state| state.name }
+
+                 match[:to].filter(values).first
+               end
+
+          return Transition.new(object, machine, name, from, to, !custom_from_state)
+        end
+      end
+
+      # No transition matched
+      nil
+    end
+
+    # Attempts to perform the next available transition on the given object.
+    # If no transitions can be made, then this will return false, otherwise
+    # true.
+    # 
+    # Any additional arguments are passed to the StateMachines::Transition#perform
+    # instance method.
+    def fire(object, *args)
+      machine.reset(object)
+
+      if transition = transition_for(object)
+        transition.perform(*args)
+      else
+        on_failure(object)
+        false
+      end
+    end
+
+    # Marks the object as invalid and runs any failure callbacks associated with
+    # this event.  This should get called anytime this event fails to transition.
+    def on_failure(object)
+      state = machine.states.match!(object)
+      machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)], [:state, state.human_name(object.class)]])
+
+      Transition.new(object, machine, name, state.name, state.name).run_callbacks(:before => false)
+    end
+
+    # Resets back to the initial state of the event, with no branches / known
+    # states associated.  This allows you to redefine an event in situations
+    # where you either are re-using an existing state machine implementation
+    # or are subclassing machines.
+    def reset
+      @branches = []
+      @known_states = []
+    end
+
+
+    def draw(graph, options = {})
+      fail NotImplementedError
+    end
+
+    # Generates a nicely formatted description of this event's contents.
+    # 
+    # For example,
+    # 
+    #   event = StateMachines::Event.new(machine, :park)
+    #   event.transition all - :idling => :parked, :idling => same
+    #   event   # => #<StateMachines::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
+    def inspect
+      transitions = branches.map do |branch|
+        branch.state_requirements.map do |state_requirement|
+          "#{state_requirement[:from].description} => #{state_requirement[:to].description}"
+        end * ', '
+      end
+
+      "#<#{self.class} name=#{name.inspect} transitions=[#{transitions * ', '}]>"
+    end
+
+    protected
+    # Add the various instance methods that can transition the object using
+    # the current event
+    def add_actions
+      # Checks whether the event can be fired on the current object
+      machine.define_helper(:instance, "can_#{qualified_name}?") do |machine, object, *args|
+        machine.event(name).can_fire?(object, *args)
+      end
+
+      # Gets the next transition that would be performed if the event were
+      # fired now
+      machine.define_helper(:instance, "#{qualified_name}_transition") do |machine, object, *args|
+        machine.event(name).transition_for(object, *args)
+      end
+
+      # Fires the event
+      machine.define_helper(:instance, qualified_name) do |machine, object, *args|
+        machine.event(name).fire(object, *args)
+      end
+
+      # Fires the event, raising an exception if it fails
+      machine.define_helper(:instance, "#{qualified_name}!") do |machine, object, *args|
+        object.send(qualified_name, *args) || raise(StateMachines::InvalidTransition.new(object, machine, name))
+      end
+    end
+  end
+end
diff --git a/lib/state_machines/event_collection.rb b/lib/state_machines/event_collection.rb
new file mode 100644
index 0000000..90b5cb2
--- /dev/null
+++ b/lib/state_machines/event_collection.rb
@@ -0,0 +1,139 @@
+module StateMachines
+  # Represents a collection of events in a state machine
+  class EventCollection < NodeCollection
+    def initialize(machine) #:nodoc:
+      super(machine, :index => [:name, :qualified_name])
+    end
+
+    # Gets the list of events that can be fired on the given object.
+    # 
+    # Valid requirement options:
+    # * <tt>:from</tt> - One or more states being transitioned from.  If none
+    #   are specified, then this will be the object's current state.
+    # * <tt>:to</tt> - One or more states being transitioned to.  If none are
+    #   specified, then this will match any to state.
+    # * <tt>:on</tt> - One or more events that fire the transition.  If none
+    #   are specified, then this will match any event.
+    # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
+    #   conditionals defined for each one.  Default is true.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :park do
+    #         transition :idling => :parked
+    #       end
+    #       
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #     end
+    #   end
+    #   
+    #   events = Vehicle.state_machine(:state).events
+    #   
+    #   vehicle = Vehicle.new               # => #<Vehicle:0xb7c464b0 @state="parked">
+    #   events.valid_for(vehicle)           # => [#<StateMachines::Event name=:ignite transitions=[:parked => :idling]>]
+    #   
+    #   vehicle.state = 'idling'
+    #   events.valid_for(vehicle)           # => [#<StateMachines::Event name=:park transitions=[:idling => :parked]>]
+    def valid_for(object, requirements = {})
+      match(requirements).select { |event| event.can_fire?(object, requirements) }
+    end
+
+    # Gets the list of transitions that can be run on the given object.
+    # 
+    # Valid requirement options:
+    # * <tt>:from</tt> - One or more states being transitioned from.  If none
+    #   are specified, then this will be the object's current state.
+    # * <tt>:to</tt> - One or more states being transitioned to.  If none are
+    #   specified, then this will match any to state.
+    # * <tt>:on</tt> - One or more events that fire the transition.  If none
+    #   are specified, then this will match any event.
+    # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
+    #   conditionals defined for each one.  Default is true.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :park do
+    #         transition :idling => :parked
+    #       end
+    #       
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #     end
+    #   end
+    #   
+    #   events = Vehicle.state_machine.events
+    #   
+    #   vehicle = Vehicle.new                             # => #<Vehicle:0xb7c464b0 @state="parked">
+    #   events.transitions_for(vehicle)                   # => [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
+    #   
+    #   vehicle.state = 'idling'
+    #   events.transitions_for(vehicle)                   # => [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
+    #   
+    #   # Search for explicit transitions regardless of the current state
+    #   events.transitions_for(vehicle, :from => :parked) # => [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
+    def transitions_for(object, requirements = {})
+      match(requirements).map { |event| event.transition_for(object, requirements) }.compact
+    end
+
+    # Gets the transition that should be performed for the event stored in the
+    # given object's event attribute.  This also takes an additional parameter
+    # for automatically invalidating the object if the event or transition are
+    # invalid.  By default, this is turned off.
+    # 
+    # *Note* that if a transition has already been generated for the event, then
+    # that transition will be used.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle < ActiveRecord::Base
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new                       # => #<Vehicle id: nil, state: "parked">
+    #   events = Vehicle.state_machine.events
+    #   
+    #   vehicle.state_event = nil
+    #   events.attribute_transition_for(vehicle)    # => nil # Event isn't defined
+    #   
+    #   vehicle.state_event = 'invalid'
+    #   events.attribute_transition_for(vehicle)    # => false # Event is invalid
+    #   
+    #   vehicle.state_event = 'ignite'
+    #   events.attribute_transition_for(vehicle)    # => #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
+    def attribute_transition_for(object, invalidate = false)
+      return unless machine.action
+
+      # TODO, simplify
+      machine.read(object, :event_transition) || if event_name = machine.read(object, :event)
+                                                   if event = self[event_name.to_sym, :name]
+                                                     event.transition_for(object) || begin
+                                                                                       # No valid transition: invalidate
+                                                       machine.invalidate(object, :event, :invalid_event, [[:state, machine.states.match!(object).human_name(object.class)]]) if invalidate
+                                                       false
+                                                     end
+                                                   else
+                                                     # Event is unknown: invalidate
+                                                     machine.invalidate(object, :event, :invalid) if invalidate
+                                                     false
+                                                   end
+                                                 end
+
+    end
+
+    private
+    def match(requirements) #:nodoc:
+      requirements && requirements[:on] ? [fetch(requirements.delete(:on))] : self
+    end
+  end
+end
diff --git a/lib/state_machines/extensions.rb b/lib/state_machines/extensions.rb
new file mode 100644
index 0000000..08f8c85
--- /dev/null
+++ b/lib/state_machines/extensions.rb
@@ -0,0 +1,148 @@
+module StateMachines
+  module ClassMethods
+    def self.extended(base) #:nodoc:
+      base.class_eval do
+        @state_machines = MachineCollection.new
+      end
+    end
+
+    # Gets the current list of state machines defined for this class.  This
+    # class-level attribute acts like an inheritable attribute.  The attribute
+    # is available to each subclass, each having a copy of its superclass's
+    # attribute.
+    #
+    # The hash of state machines maps <tt>:attribute</tt> => +machine+, e.g.
+    #
+    #   Vehicle.state_machines # => {:state => #<StateMachines::Machine:0xb6f6e4a4 ...>}
+    def state_machines
+      @state_machines ||= superclass.state_machines.dup
+    end
+  end
+
+  module InstanceMethods
+    # Runs one or more events in parallel.  All events will run through the
+    # following steps:
+    # * Before callbacks
+    # * Persist state
+    # * Invoke action
+    # * After callbacks
+    #
+    # For example, if two events (for state machines A and B) are run in
+    # parallel, the order in which steps are run is:
+    # * A - Before transition callbacks
+    # * B - Before transition callbacks
+    # * A - Persist new state
+    # * B - Persist new state
+    # * A - Invoke action
+    # * B - Invoke action (only if different than A's action)
+    # * A - After transition callbacks
+    # * B - After transition callbacks
+    #
+    # *Note* that multiple events on the same state machine / attribute cannot
+    # be run in parallel.  If this is attempted, an ArgumentError will be
+    # raised.
+    #
+    # == Halting callbacks
+    #
+    # When running multiple events in parallel, special consideration should
+    # be taken with regard to how halting within callbacks affects the flow.
+    #
+    # For *before* callbacks, any <tt>:halt</tt> error that's thrown will
+    # immediately cancel the perform for all transitions.  As a result, it's
+    # possible for one event's transition to affect the continuation of
+    # another.
+    #
+    # On the other hand, any <tt>:halt</tt> error that's thrown within an
+    # *after* callback with only affect that event's transition.  Other
+    # transitions will continue to run their own callbacks.
+    #
+    # == Example
+    #
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #
+    #       event :park do
+    #         transition :idling => :parked
+    #       end
+    #     end
+    #
+    #     state_machine :alarm_state, :namespace => 'alarm', :initial => :on do
+    #       event :enable do
+    #         transition all => :active
+    #       end
+    #
+    #       event :disable do
+    #         transition all => :off
+    #       end
+    #     end
+    #   end
+    #
+    #   vehicle = Vehicle.new                         # => #<Vehicle:0xb7c02850 @state="parked", @alarm_state="active">
+    #   vehicle.state                                 # => "parked"
+    #   vehicle.alarm_state                           # => "active"
+    #
+    #   vehicle.fire_events(:ignite, :disable_alarm)  # => true
+    #   vehicle.state                                 # => "idling"
+    #   vehicle.alarm_state                           # => "off"
+    #
+    #   # If any event fails, the entire event chain fails
+    #   vehicle.fire_events(:ignite, :enable_alarm)   # => false
+    #   vehicle.state                                 # => "idling"
+    #   vehicle.alarm_state                           # => "off"
+    #
+    #   # Exception raised on invalid event
+    #   vehicle.fire_events(:park, :invalid)          # => StateMachines::InvalidEvent: :invalid is an unknown event
+    #   vehicle.state                                 # => "idling"
+    #   vehicle.alarm_state                           # => "off"
+    def fire_events(*events)
+      self.class.state_machines.fire_events(self, *events)
+    end
+
+    # Run one or more events in parallel.  If any event fails to run, then
+    # a StateMachines::InvalidTransition exception will be raised.
+    #
+    # See StateMachines::InstanceMethods#fire_events for more information.
+    #
+    # == Example
+    #
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #
+    #       event :park do
+    #         transition :idling => :parked
+    #       end
+    #     end
+    #
+    #     state_machine :alarm_state, :namespace => 'alarm', :initial => :active do
+    #       event :enable do
+    #         transition all => :active
+    #       end
+    #
+    #       event :disable do
+    #         transition all => :off
+    #       end
+    #     end
+    #   end
+    #
+    #   vehicle = Vehicle.new                         # => #<Vehicle:0xb7c02850 @state="parked", @alarm_state="active">
+    #   vehicle.fire_events(:ignite, :disable_alarm)  # => true
+    #
+    #   vehicle.fire_events!(:ignite, :disable_alarm) # => StateMachines::InvalidTranstion: Cannot run events in parallel: ignite, disable_alarm
+    def fire_events!(*events)
+      run_action = [true, false].include?(events.last) ? events.pop : true
+      fire_events(*(events + [run_action])) || fail(StateMachines::InvalidParallelTransition.new(self, events))
+    end
+
+    protected
+
+    def initialize_state_machines(options = {}, &block) #:nodoc:
+      self.class.state_machines.initialize_states(self, options, &block)
+    end
+  end
+end
diff --git a/lib/state_machines/helper_module.rb b/lib/state_machines/helper_module.rb
new file mode 100644
index 0000000..e7c3e89
--- /dev/null
+++ b/lib/state_machines/helper_module.rb
@@ -0,0 +1,17 @@
+module StateMachines
+  # Represents a type of module that defines instance / class methods for a
+  # state machine
+  class HelperModule < Module #:nodoc:
+    def initialize(machine, kind)
+      @machine = machine
+      @kind = kind
+    end
+    
+    # Provides a human-readable description of the module
+    def to_s
+      owner_class = @machine.owner_class
+      owner_class_name = owner_class.name && !owner_class.name.empty? ? owner_class.name : owner_class.to_s
+      "#{owner_class_name} #{@machine.name.inspect} #{@kind} helpers"
+    end
+  end
+end
diff --git a/lib/state_machines/integrations.rb b/lib/state_machines/integrations.rb
new file mode 100644
index 0000000..e271bc8
--- /dev/null
+++ b/lib/state_machines/integrations.rb
@@ -0,0 +1,116 @@
+require 'set'
+
+module StateMachines
+  # Integrations allow state machines to take advantage of features within the
+  # context of a particular library.  This is currently most useful with
+  # database libraries.  For example, the various database integrations allow
+  # state machines to hook into features like:
+  # * Saving
+  # * Transactions
+  # * Observers
+  # * Scopes
+  # * Callbacks
+  # * Validation errors
+  # 
+  # This type of integration allows the user to work with state machines in a
+  # fashion similar to other object models in their application.
+  # 
+  # The integration interface is loosely defined by various unimplemented
+  # methods in the StateMachines::Machine class.  See that class or the various
+  # built-in integrations for more information about how to define additional
+  # integrations.
+  module Integrations
+    @integrations = []
+
+    class << self
+      #  Register integration
+      def register(name_or_module)
+        case name_or_module.class.to_s
+          when 'Module'
+            add(name_or_module)
+          else
+            fail IntegrationError
+        end
+        true
+      end
+
+      def reset #:nodoc:#
+        @integrations = []
+      end
+
+      # Gets a list of all of the available integrations for use.
+      #
+      # == Example
+      #
+      #   StateMachines::Integrations.integrations
+      #   # => []
+      #   StateMachines::Integrations.register(StateMachines::Integrations::ActiveModel)
+      #   StateMachines::Integrations.integrations
+      #   # => [StateMachines::Integrations::ActiveModel]
+      def integrations
+        # Register all namespaced integrations
+        @integrations
+      end
+
+      alias_method :list, :integrations
+
+
+      # Attempts to find an integration that matches the given class.  This will
+      # look through all of the built-in integrations under the StateMachines::Integrations
+      # namespace and find one that successfully matches the class.
+      # 
+      # == Examples
+      # 
+      #   class Vehicle
+      #   end
+      #   
+      #   class ActiveModelVehicle
+      #     include ActiveModel::Observing
+      #     include ActiveModel::Validations
+      #   end
+      #   
+      #   class ActiveRecordVehicle < ActiveRecord::Base
+      #   end
+      #   
+      #   StateMachines::Integrations.match(Vehicle)             # => nil
+      #   StateMachines::Integrations.match(ActiveModelVehicle)  # => StateMachines::Integrations::ActiveModel
+      #   StateMachines::Integrations.match(ActiveRecordVehicle) # => StateMachines::Integrations::ActiveRecord
+      def match(klass)
+        integrations.detect { |integration| integration.matches?(klass) }
+      end
+
+      # Attempts to find an integration that matches the given list of ancestors.
+      # This will look through all of the built-in integrations under the StateMachines::Integrations
+      # namespace and find one that successfully matches one of the ancestors.
+      #
+      # == Examples
+      #
+      #   StateMachines::Integrations.match_ancestors([])                    # => nil
+      #   StateMachines::Integrations.match_ancestors(['ActiveRecord::Base']) # => StateMachines::Integrations::ActiveModel
+      def match_ancestors(ancestors)
+        integrations.detect { |integration| integration.matches_ancestors?(ancestors) }
+      end
+
+      # Finds an integration with the given name.  If the integration cannot be
+      # found, then a NameError exception will be raised.
+      #
+      # == Examples
+      #
+      #   StateMachines::Integrations.find_by_name(:active_model)  # => StateMachines::Integrations::ActiveModel
+      #   StateMachines::Integrations.find_by_name(:active_record) # => StateMachines::Integrations::ActiveRecord
+      #   StateMachines::Integrations.find_by_name(:invalid)       # => StateMachines::IntegrationNotFound: :invalid is an invalid integration
+      def find_by_name(name)
+        integrations.detect { |integration| integration.integration_name == name } || raise(IntegrationNotFound.new(name))
+      end
+
+
+      private
+
+      def add(integration)
+        if integration.respond_to?(:integration_name)
+          @integrations.insert(0, integration) unless @integrations.include?(integration)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/state_machines/integrations/base.rb b/lib/state_machines/integrations/base.rb
new file mode 100644
index 0000000..dbe0324
--- /dev/null
+++ b/lib/state_machines/integrations/base.rb
@@ -0,0 +1,44 @@
+module StateMachines
+  module Integrations
+    # Provides a set of base helpers for managing individual integrations
+    module Base
+      module ClassMethods
+        # The default options to use for state machines using this integration
+        attr_reader :defaults
+
+        # The name of the integration
+        def integration_name
+          @integration_name ||= begin
+            name = self.name.split('::').last
+            name.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
+            name.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
+            name.downcase!
+            name.to_sym
+          end
+        end
+
+        # The list of ancestor names that cause this integration to matched.
+        def matching_ancestors
+          []
+        end
+
+        # Whether the integration should be used for the given class.
+        def matches?(klass)
+          matches_ancestors?(klass.ancestors.map { |ancestor| ancestor.name })
+        end
+
+        # Whether the integration should be used for the given list of ancestors.
+        def matches_ancestors?(ancestors)
+          (ancestors & matching_ancestors).any?
+        end
+
+      end
+
+      extend ClassMethods
+
+      def self.included(base) #:nodoc:
+        base.class_eval { extend ClassMethods }
+      end
+    end
+  end
+end
diff --git a/lib/state_machines/machine.rb b/lib/state_machines/machine.rb
new file mode 100644
index 0000000..44648c6
--- /dev/null
+++ b/lib/state_machines/machine.rb
@@ -0,0 +1,2230 @@
+module StateMachines
+  # Represents a state machine for a particular attribute.  State machines
+  # consist of states, events and a set of transitions that define how the
+  # state changes after a particular event is fired.
+  # 
+  # A state machine will not know all of the possible states for an object
+  # unless they are referenced *somewhere* in the state machine definition.
+  # As a result, any unused states should be defined with the +other_states+
+  # or +state+ helper.
+  # 
+  # == Actions
+  # 
+  # When an action is configured for a state machine, it is invoked when an
+  # object transitions via an event.  The success of the event becomes
+  # dependent on the success of the action.  If the action is successful, then
+  # the transitioned state remains persisted.  However, if the action fails
+  # (by returning false), the transitioned state will be rolled back.
+  # 
+  # For example,
+  # 
+  #   class Vehicle
+  #     attr_accessor :fail, :saving_state
+  #     
+  #     state_machine :initial => :parked, :action => :save do
+  #       event :ignite do
+  #         transition :parked => :idling
+  #       end
+  #       
+  #       event :park do
+  #         transition :idling => :parked
+  #       end
+  #     end
+  #     
+  #     def save
+  #       @saving_state = state
+  #       fail != true
+  #     end
+  #   end
+  #   
+  #   vehicle = Vehicle.new     # => #<Vehicle:0xb7c27024 @state="parked">
+  #   vehicle.save              # => true
+  #   vehicle.saving_state      # => "parked" # The state was "parked" was save was called
+  #   
+  #   # Successful event
+  #   vehicle.ignite            # => true
+  #   vehicle.saving_state      # => "idling" # The state was "idling" when save was called
+  #   vehicle.state             # => "idling"
+  #   
+  #   # Failed event
+  #   vehicle.fail = true
+  #   vehicle.park              # => false
+  #   vehicle.saving_state      # => "parked"
+  #   vehicle.state             # => "idling"
+  # 
+  # As shown, even though the state is set prior to calling the +save+ action
+  # on the object, it will be rolled back to the original state if the action
+  # fails.  *Note* that this will also be the case if an exception is raised
+  # while calling the action.
+  # 
+  # === Indirect transitions
+  # 
+  # In addition to the action being run as the _result_ of an event, the action
+  # can also be used to run events itself.  For example, using the above as an
+  # example:
+  # 
+  #   vehicle = Vehicle.new           # => #<Vehicle:0xb7c27024 @state="parked">
+  #   
+  #   vehicle.state_event = 'ignite'
+  #   vehicle.save                    # => true
+  #   vehicle.state                   # => "idling"
+  #   vehicle.state_event             # => nil
+  # 
+  # As can be seen, the +save+ action automatically invokes the event stored in
+  # the +state_event+ attribute (<tt>:ignite</tt> in this case).
+  # 
+  # One important note about using this technique for running transitions is
+  # that if the class in which the state machine is defined *also* defines the
+  # action being invoked (and not a superclass), then it must manually run the
+  # StateMachine hook that checks for event attributes.
+  # 
+  # For example, in ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel,
+  # the default action (+save+) is already defined in a base class.  As a result,
+  # when a state machine is defined in a model / resource, StateMachine can
+  # automatically hook into the +save+ action.
+  # 
+  # On the other hand, the Vehicle class from above defined its own +save+
+  # method (and there is no +save+ method in its superclass).  As a result, it
+  # must be modified like so:
+  # 
+  #     def save
+  #       self.class.state_machines.transitions(self, :save).perform do
+  #         @saving_state = state
+  #         fail != true
+  #       end
+  #     end
+  # 
+  # This will add in the functionality for firing the event stored in the
+  # +state_event+ attribute.
+  # 
+  # == Callbacks
+  # 
+  # Callbacks are supported for hooking before and after every possible
+  # transition in the machine.  Each callback is invoked in the order in which
+  # it was defined.  See StateMachines::Machine#before_transition and
+  # StateMachines::Machine#after_transition for documentation on how to define
+  # new callbacks.
+  # 
+  # *Note* that callbacks only get executed within the context of an event.  As
+  # a result, if a class has an initial state when it's created, any callbacks
+  # that would normally get executed when the object enters that state will
+  # *not* get triggered.
+  # 
+  # For example,
+  # 
+  #   class Vehicle
+  #     state_machine :initial => :parked do
+  #       after_transition all => :parked do
+  #         raise ArgumentError
+  #       end
+  #       ...
+  #     end
+  #   end
+  #   
+  #   vehicle = Vehicle.new   # => #<Vehicle id: 1, state: "parked">
+  #   vehicle.save            # => true (no exception raised)
+  # 
+  # If you need callbacks to get triggered when an object is created, this
+  # should be done by one of the following techniques:
+  # * Use a <tt>before :create</tt> or equivalent hook:
+  # 
+  #     class Vehicle
+  #       before :create, :track_initial_transition
+  #       
+  #       state_machine do
+  #         ...
+  #       end
+  #     end
+  # 
+  # * Set an initial state and use the correct event to create the
+  #   object with the proper state, resulting in callbacks being triggered and
+  #   the object getting persisted (note that the <tt>:pending</tt> state is
+  #   actually stored as nil):
+  # 
+  #     class Vehicle
+  #        state_machine :initial => :pending
+  #         after_transition :pending => :parked, :do => :track_initial_transition
+  #         
+  #         event :park do
+  #           transition :pending => :parked
+  #         end
+  #         
+  #         state :pending, :value => nil
+  #       end
+  #     end
+  #     
+  #     vehicle = Vehicle.new
+  #     vehicle.park
+  # 
+  # * Use a default event attribute that will automatically trigger when the
+  #   configured action gets run (note that the <tt>:pending</tt> state is
+  #   actually stored as nil):
+  # 
+  #     class Vehicle < ActiveRecord::Base
+  #       state_machine :initial => :pending
+  #         after_transition :pending => :parked, :do => :track_initial_transition
+  #         
+  #         event :park do
+  #           transition :pending => :parked
+  #         end
+  #         
+  #         state :pending, :value => nil
+  #       end
+  #       
+  #       def initialize(*)
+  #         super
+  #         self.state_event = 'park'
+  #       end
+  #     end
+  #     
+  #     vehicle = Vehicle.new
+  #     vehicle.save
+  # 
+  # === Canceling callbacks
+  # 
+  # Callbacks can be canceled by throwing :halt at any point during the
+  # callback.  For example,
+  # 
+  #   ...
+  #   throw :halt
+  #   ...
+  # 
+  # If a +before+ callback halts the chain, the associated transition and all
+  # later callbacks are canceled.  If an +after+ callback halts the chain,
+  # the later callbacks are canceled, but the transition is still successful.
+  # 
+  # These same rules apply to +around+ callbacks with the exception that any
+  # +around+ callback that doesn't yield will essentially result in :halt being
+  # thrown.  Any code executed after the yield will behave in the same way as
+  # +after+ callbacks.
+  # 
+  # *Note* that if a +before+ callback fails and the bang version of an event
+  # was invoked, an exception will be raised instead of returning false.  For
+  # example,
+  # 
+  #   class Vehicle
+  #     state_machine :initial => :parked do
+  #       before_transition any => :idling, :do => lambda {|vehicle| throw :halt}
+  #       ...
+  #     end
+  #   end
+  #   
+  #   vehicle = Vehicle.new
+  #   vehicle.park        # => false
+  #   vehicle.park!       # => StateMachines::InvalidTransition: Cannot transition state via :park from "idling"
+  # 
+  # == Observers
+  # 
+  # Observers, in the sense of external classes and *not* Ruby's Observable
+  # mechanism, can hook into state machines as well.  Such observers use the
+  # same callback api that's used internally.
+  # 
+  # Below are examples of defining observers for the following state machine:
+  # 
+  #   class Vehicle
+  #     state_machine do
+  #       event :park do
+  #         transition :idling => :parked
+  #       end
+  #       ...
+  #     end
+  #     ...
+  #   end
+  # 
+  # Event/Transition behaviors:
+  # 
+  #   class VehicleObserver
+  #     def self.before_park(vehicle, transition)
+  #       logger.info "#{vehicle} instructed to park... state is: #{transition.from}, state will be: #{transition.to}"
+  #     end
+  #     
+  #     def self.after_park(vehicle, transition, result)
+  #       logger.info "#{vehicle} instructed to park... state was: #{transition.from}, state is: #{transition.to}"
+  #     end
+  #     
+  #     def self.before_transition(vehicle, transition)
+  #       logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} is: #{transition.from}, #{transition.attribute} will be: #{transition.to}"
+  #     end
+  #     
+  #     def self.after_transition(vehicle, transition)
+  #       logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} was: #{transition.from}, #{transition.attribute} is: #{transition.to}"
+  #     end
+  #     
+  #     def self.around_transition(vehicle, transition)
+  #       logger.info Benchmark.measure { yield }
+  #     end
+  #   end
+  #   
+  #   Vehicle.state_machine do
+  #     before_transition :on => :park, :do => VehicleObserver.method(:before_park)
+  #     before_transition VehicleObserver.method(:before_transition)
+  #     
+  #     after_transition :on => :park, :do => VehicleObserver.method(:after_park)
+  #     after_transition VehicleObserver.method(:after_transition)
+  #     
+  #     around_transition VehicleObserver.method(:around_transition)
+  #   end
+  # 
+  # One common callback is to record transitions for all models in the system
+  # for auditing/debugging purposes.  Below is an example of an observer that
+  # can easily automate this process for all models:
+  # 
+  #   class StateMachineObserver
+  #     def self.before_transition(object, transition)
+  #       Audit.log_transition(object.attributes)
+  #     end
+  #   end
+  #   
+  #   [Vehicle, Switch, Project].each do |klass|
+  #     klass.state_machines.each do |attribute, machine|
+  #       machine.before_transition StateMachineObserver.method(:before_transition)
+  #     end
+  #   end
+  # 
+  # Additional observer-like behavior may be exposed by the various integrations
+  # available.  See below for more information on integrations.
+  # 
+  # == Overriding instance / class methods
+  # 
+  # Hooking in behavior to the generated instance / class methods from the
+  # state machine, events, and states is very simple because of the way these
+  # methods are generated on the class.  Using the class's ancestors, the
+  # original generated method can be referred to via +super+.  For example,
+  # 
+  #   class Vehicle
+  #     state_machine do
+  #       event :park do
+  #         ...
+  #       end
+  #     end
+  #     
+  #     def park(*args)
+  #       logger.info "..."
+  #       super
+  #     end
+  #   end
+  # 
+  # In the above example, the +park+ instance method that's generated on the
+  # Vehicle class (by the associated event) is overridden with custom behavior.
+  # Once this behavior is complete, the original method from the state machine
+  # is invoked by simply calling +super+.
+  # 
+  # The same technique can be used for +state+, +state_name+, and all other
+  # instance *and* class methods on the Vehicle class.
+  #
+  # == Method conflicts
+  # 
+  # By default state_machine does not redefine methods that exist on
+  # superclasses (*including* Object) or any modules (*including* Kernel) that
+  # were included before it was defined.  This is in order to ensure that
+  # existing behavior on the class is not broken by the inclusion of
+  # state_machine.
+  # 
+  # If a conflicting method is detected, state_machine will generate a warning.
+  # For example, consider the following class:
+  # 
+  #   class Vehicle
+  #     state_machine do
+  #       event :open do
+  #         ...
+  #       end
+  #     end
+  #   end
+  # 
+  # In the above class, an event named "open" is defined for its state machine.
+  # However, "open" is already defined as an instance method in Ruby's Kernel
+  # module that gets included in every Object.  As a result, state_machine will
+  # generate the following warning:
+  # 
+  #   Instance method "open" is already defined in Object, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.
+  # 
+  # Even though you may not be using Kernel's implementation of the "open"
+  # instance method, state_machine isn't aware of this and, as a result, stays
+  # safe and just skips redefining the method.
+  # 
+  # As with almost all helpers methods defined by state_machine in your class,
+  # there are generic methods available for working around this method conflict.
+  # In the example above, you can invoke the "open" event like so:
+  # 
+  #   vehicle = Vehicle.new       # => #<Vehicle:0xb72686b4 @state=nil>
+  #   vehicle.fire_events(:open)  # => true
+  #   
+  #   # This will not work
+  #   vehicle.open                # => NoMethodError: private method `open' called for #<Vehicle:0xb72686b4 @state=nil>
+  # 
+  # If you want to take on the risk of overriding existing methods and just
+  # ignore method conflicts altogether, you can do so by setting the following
+  # configuration:
+  # 
+  #   StateMachines::Machine.ignore_method_conflicts = true
+  # 
+  # This will allow you to define events like "open" as described above and
+  # still generate the "open" instance helper method.  For example:
+  # 
+  #   StateMachines::Machine.ignore_method_conflicts = true
+  #   
+  #   class Vehicle
+  #     state_machine do
+  #       event :open do
+  #         ...
+  #     end
+  #   end
+  #   
+  #   vehicle = Vehicle.new   # => #<Vehicle:0xb72686b4 @state=nil>
+  #   vehicle.open            # => true
+  # 
+  # By default, state_machine helps prevent you from making mistakes and
+  # accidentally overriding methods that you didn't intend to.  Once you
+  # understand this and what the consequences are, setting the
+  # +ignore_method_conflicts+ option is a perfectly reasonable workaround.
+  # 
+  # == Integrations
+  # 
+  # By default, state machines are library-agnostic, meaning that they work
+  # on any Ruby class and have no external dependencies.  However, there are
+  # certain libraries which expose additional behavior that can be taken
+  # advantage of by state machines.
+  # 
+  # This library is built to work out of the box with a few popular Ruby
+  # libraries that allow for additional behavior to provide a cleaner and
+  # smoother experience.  This is especially the case for objects backed by a
+  # database that may allow for transactions, persistent storage,
+  # search/filters, callbacks, etc.
+  # 
+  # When a state machine is defined for classes using any of the above libraries,
+  # it will try to automatically determine the integration to use (Agnostic,
+  # ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, or Sequel)
+  # based on the class definition.  To see how each integration affects the
+  # machine's behavior, refer to all constants defined under the
+  # StateMachines::Integrations namespace.
+  class Machine
+
+    include EvalHelpers
+    include MatcherHelpers
+
+    class << self
+      # Attempts to find or create a state machine for the given class.  For
+      # example,
+      # 
+      #   StateMachines::Machine.find_or_create(Vehicle)
+      #   StateMachines::Machine.find_or_create(Vehicle, :initial => :parked)
+      #   StateMachines::Machine.find_or_create(Vehicle, :status)
+      #   StateMachines::Machine.find_or_create(Vehicle, :status, :initial => :parked)
+      # 
+      # If a machine of the given name already exists in one of the class's
+      # superclasses, then a copy of that machine will be created and stored
+      # in the new owner class (the original will remain unchanged).
+      def find_or_create(owner_class, *args, &block)
+        options = args.last.is_a?(Hash) ? args.pop : {}
+        name = args.first || :state
+
+        # Find an existing machine
+        if owner_class.respond_to?(:state_machines) && machine = owner_class.state_machines[name]
+          # Only create a new copy if changes are being made to the machine in
+          # a subclass
+          if machine.owner_class != owner_class && (options.any? || block_given?)
+            machine = machine.clone
+            machine.initial_state = options[:initial] if options.include?(:initial)
+            machine.owner_class = owner_class
+          end
+
+          # Evaluate DSL
+          machine.instance_eval(&block) if block_given?
+        else
+          # No existing machine: create a new one
+          machine = new(owner_class, name, options, &block)
+        end
+
+        machine
+      end
+
+
+      def draw(*)
+        fail NotImplementedError
+      end
+
+      # Default messages to use for validation errors in ORM integrations
+      attr_accessor :default_messages
+      attr_accessor :ignore_method_conflicts
+    end
+    @default_messages = {
+        :invalid => 'is invalid',
+        :invalid_event => 'cannot transition when %s',
+        :invalid_transition => 'cannot transition via "%1$s"'
+    }
+
+    # Whether to ignore any conflicts that are detected for helper methods that
+    # get generated for a machine's owner class.  Default is false.
+    @ignore_method_conflicts = false
+
+    # The class that the machine is defined in
+    attr_reader :owner_class
+
+    # The name of the machine, used for scoping methods generated for the
+    # machine as a whole (not states or events)
+    attr_reader :name
+
+    # The events that trigger transitions.  These are sorted, by default, in
+    # the order in which they were defined.
+    attr_reader :events
+
+    # A list of all of the states known to this state machine.  This will pull
+    # states from the following sources:
+    # * Initial state
+    # * State behaviors
+    # * Event transitions (:to, :from, and :except_from options)
+    # * Transition callbacks (:to, :from, :except_to, and :except_from options)
+    # * Unreferenced states (using +other_states+ helper)
+    # 
+    # These are sorted, by default, in the order in which they were referenced.
+    attr_reader :states
+
+    # The callbacks to invoke before/after a transition is performed
+    # 
+    # Maps :before => callbacks and :after => callbacks
+    attr_reader :callbacks
+
+    # The action to invoke when an object transitions
+    attr_reader :action
+
+    # An identifier that forces all methods (including state predicates and
+    # event methods) to be generated with the value prefixed or suffixed,
+    # depending on the context.
+    attr_reader :namespace
+
+    # Whether the machine will use transactions when firing events
+    attr_reader :use_transactions
+
+    # Creates a new state machine for the given attribute
+    def initialize(owner_class, *args, &block)
+      options = args.last.is_a?(Hash) ? args.pop : {}
+      options.assert_valid_keys(:attribute, :initial, :initialize, :action, :plural, :namespace, :integration, :messages, :use_transactions)
+
+      # Find an integration that matches this machine's owner class
+      if options.include?(:integration)
+        @integration = options[:integration] && StateMachines::Integrations.find_by_name(options[:integration])
+      else
+        @integration = StateMachines::Integrations.match(owner_class)
+      end
+
+      if @integration
+        extend @integration
+        options = (@integration.defaults || {}).merge(options)
+      end
+
+      # Add machine-wide defaults
+      options = {:use_transactions => true, :initialize => true}.merge(options)
+
+      # Set machine configuration
+      @name = args.first || :state
+      @attribute = options[:attribute] || @name
+      @events = EventCollection.new(self)
+      @states = StateCollection.new(self)
+      @callbacks = {:before => [], :after => [], :failure => []}
+      @namespace = options[:namespace]
+      @messages = options[:messages] || {}
+      @action = options[:action]
+      @use_transactions = options[:use_transactions]
+      @initialize_state = options[:initialize]
+      @action_hook_defined = false
+      self.owner_class = owner_class
+
+      # Merge with sibling machine configurations
+      add_sibling_machine_configs
+
+      # Define class integration
+      define_helpers
+      define_scopes(options[:plural])
+      after_initialize
+
+      # Evaluate DSL
+      instance_eval(&block) if block_given?
+      self.initial_state = options[:initial] unless sibling_machines.any?
+    end
+
+    # Creates a copy of this machine in addition to copies of each associated
+    # event/states/callback, so that the modifications to those collections do
+    # not affect the original machine.
+    def initialize_copy(orig) #:nodoc:
+      super
+
+      @events = @events.dup
+      @events.machine = self
+      @states = @states.dup
+      @states.machine = self
+      @callbacks = {:before => @callbacks[:before].dup, :after => @callbacks[:after].dup, :failure => @callbacks[:failure].dup}
+    end
+
+    # Sets the class which is the owner of this state machine.  Any methods
+    # generated by states, events, or other parts of the machine will be defined
+    # on the given owner class.
+    def owner_class=(klass)
+      @owner_class = klass
+
+      # Create modules for extending the class with state/event-specific methods
+      @helper_modules = helper_modules = {:instance => HelperModule.new(self, :instance), :class => HelperModule.new(self, :class)}
+      owner_class.class_eval do
+        extend helper_modules[:class]
+        include helper_modules[:instance]
+      end
+
+      # Add class-/instance-level methods to the owner class for state initialization
+      unless owner_class < StateMachines::InstanceMethods
+        owner_class.class_eval do
+          extend StateMachines::ClassMethods
+          include StateMachines::InstanceMethods
+        end
+
+        define_state_initializer if @initialize_state
+      end
+
+      # Record this machine as matched to the name in the current owner class.
+      # This will override any machines mapped to the same name in any superclasses.
+      owner_class.state_machines[name] = self
+    end
+
+    # Sets the initial state of the machine.  This can be either the static name
+    # of a state or a lambda block which determines the initial state at
+    # creation time.
+    def initial_state=(new_initial_state)
+      @initial_state = new_initial_state
+      add_states([@initial_state]) unless dynamic_initial_state?
+
+      # Update all states to reflect the new initial state
+      states.each { |state| state.initial = (state.name == @initial_state) }
+
+      # Output a warning if there are conflicting initial states for the machine's
+      # attribute
+      initial_state = states.detect { |state| state.initial }
+      if !owner_class_attribute_default.nil? && (dynamic_initial_state? || !owner_class_attribute_default_matches?(initial_state))
+        warn(
+            "Both #{owner_class.name} and its #{name.inspect} machine have defined "\
+          "a different default for \"#{attribute}\". Use only one or the other for "\
+          "defining defaults to avoid unexpected behaviors."
+        )
+      end
+    end
+
+    # Gets the initial state of the machine for the given object. If a dynamic
+    # initial state was configured for this machine, then the object will be
+    # passed into the lambda block to help determine the actual state.
+    # 
+    # == Examples
+    # 
+    # With a static initial state:
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       ...
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new
+    #   Vehicle.state_machine.initial_state(vehicle)  # => #<StateMachines::State name=:parked value="parked" initial=true>
+    # 
+    # With a dynamic initial state:
+    # 
+    #   class Vehicle
+    #     attr_accessor :force_idle
+    #     
+    #     state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do
+    #       ...
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new
+    #   
+    #   vehicle.force_idle = true
+    #   Vehicle.state_machine.initial_state(vehicle)  # => #<StateMachines::State name=:idling value="idling" initial=false>
+    #   
+    #   vehicle.force_idle = false
+    #   Vehicle.state_machine.initial_state(vehicle)  # => #<StateMachines::State name=:parked value="parked" initial=false>
+    def initial_state(object)
+      states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state) if instance_variable_defined?('@initial_state')
+    end
+
+    # Whether a dynamic initial state is being used in the machine
+    def dynamic_initial_state?
+      instance_variable_defined?('@initial_state') && @initial_state.is_a?(Proc)
+    end
+
+    # Initializes the state on the given object.  Initial values are only set if
+    # the machine's attribute hasn't been previously initialized.
+    # 
+    # Configuration options:
+    # * <tt>:force</tt> - Whether to initialize the state regardless of its
+    #   current value
+    # * <tt>:to</tt> - A hash to set the initial value in instead of writing
+    #   directly to the object
+    def initialize_state(object, options = {})
+      state = initial_state(object)
+      if state && (options[:force] || initialize_state?(object))
+        value = state.value
+
+        if hash = options[:to]
+          hash[attribute.to_s] = value
+        else
+          write(object, :state, value)
+        end
+      end
+    end
+
+    # Gets the actual name of the attribute on the machine's owner class that
+    # stores data with the given name.
+    def attribute(name = :state)
+      name == :state ? @attribute : :"#{self.name}_#{name}"
+    end
+
+    # Defines a new helper method in an instance or class scope with the given
+    # name.  If the method is already defined in the scope, then this will not
+    # override it.
+    # 
+    # If passing in a block, there are two side effects to be aware of
+    # 1. The method cannot be chained, meaning that the block cannot call +super+
+    # 2. If the method is already defined in an ancestor, then it will not get
+    #    overridden and a warning will be output.
+    # 
+    # Example:
+    # 
+    #   # Instance helper
+    #   machine.define_helper(:instance, :state_name) do |machine, object|
+    #     machine.states.match(object).name
+    #   end
+    #   
+    #   # Class helper
+    #   machine.define_helper(:class, :state_machine_name) do |machine, klass|
+    #     "State"
+    #   end
+    # 
+    # You can also define helpers using string evaluation like so:
+    # 
+    #   # Instance helper
+    #   machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+    #     def state_name
+    #       self.class.state_machine(:state).states.match(self).name
+    #     end
+    #   end_eval
+    #   
+    #   # Class helper
+    #   machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
+    #     def state_machine_name
+    #       "State"
+    #     end
+    #   end_eval
+    def define_helper(scope, method, *args, &block)
+      helper_module = @helper_modules.fetch(scope)
+
+      if block_given?
+        if !self.class.ignore_method_conflicts && conflicting_ancestor = owner_class_ancestor_has_method?(scope, method)
+          ancestor_name = conflicting_ancestor.name && !conflicting_ancestor.name.empty? ? conflicting_ancestor.name : conflicting_ancestor.to_s
+          warn "#{scope == :class ? 'Class' : 'Instance'} method \"#{method}\" is already defined in #{ancestor_name}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true."
+        else
+          name = self.name
+          helper_module.class_eval do
+            define_method(method) do |*block_args|
+              block.call((scope == :instance ? self.class : self).state_machine(name), self, *block_args)
+            end
+          end
+        end
+      else
+        helper_module.class_eval(method, *args)
+      end
+    end
+
+    # Customizes the definition of one or more states in the machine.
+    # 
+    # Configuration options:
+    # * <tt>:value</tt> - The actual value to store when an object transitions
+    #   to the state.  Default is the name (stringified).
+    # * <tt>:cache</tt> - If a dynamic value (via a lambda block) is being used,
+    #   then setting this to true will cache the evaluated result
+    # * <tt>:if</tt> - Determines whether an object's value matches the state
+    #   (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
+    #   By default, the configured value is matched.
+    # * <tt>:human_name</tt> - The human-readable version of this state's name.
+    #   By default, this is either defined by the integration or stringifies the
+    #   name and converts underscores to spaces.
+    # 
+    # == Customizing the stored value
+    # 
+    # Whenever a state is automatically discovered in the state machine, its
+    # default value is assumed to be the stringified version of the name.  For
+    # example,
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #     end
+    #   end
+    # 
+    # In the above state machine, there are two states automatically discovered:
+    # :parked and :idling.  These states, by default, will store their stringified
+    # equivalents when an object moves into that state (e.g. "parked" / "idling").
+    # 
+    # For legacy systems or when tying state machines into existing frameworks,
+    # it's oftentimes necessary to need to store a different value for a state
+    # than the default.  In order to continue taking advantage of an expressive
+    # state machine and helper methods, every defined state can be re-configured
+    # with a custom stored value.  For example,
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #       
+    #       state :idling, :value => 'IDLING'
+    #       state :parked, :value => 'PARKED
+    #     end
+    #   end
+    # 
+    # This is also useful if being used in association with a database and,
+    # instead of storing the state name in a column, you want to store the
+    # state's foreign key:
+    # 
+    #   class VehicleState < ActiveRecord::Base
+    #   end
+    #   
+    #   class Vehicle < ActiveRecord::Base
+    #     state_machine :attribute => :state_id, :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #       
+    #       states.each do |state|
+    #         self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true)
+    #       end
+    #     end
+    #   end
+    # 
+    # In the above example, each known state is configured to store it's
+    # associated database id in the +state_id+ attribute.  Also, notice that a
+    # lambda block is used to define the state's value.  This is required in
+    # situations (like testing) where the model is loaded without any existing
+    # data (i.e. no VehicleState records available).
+    # 
+    # One caveat to the above example is to keep performance in mind.  To avoid
+    # constant db hits for looking up the VehicleState ids, the value is cached
+    # by specifying the <tt>:cache</tt> option.  Alternatively, a custom
+    # caching strategy can be used like so:
+    # 
+    #   class VehicleState < ActiveRecord::Base
+    #     cattr_accessor :cache_store
+    #     self.cache_store = ActiveSupport::Cache::MemoryStore.new
+    #     
+    #     def self.find_by_name(name)
+    #       cache_store.fetch(name) { find(:first, :conditions => {:name => name}) }
+    #     end
+    #   end
+    # 
+    # === Dynamic values
+    # 
+    # In addition to customizing states with other value types, lambda blocks
+    # can also be specified to allow for a state's value to be determined
+    # dynamically at runtime.  For example,
+    # 
+    #   class Vehicle
+    #     state_machine :purchased_at, :initial => :available do
+    #       event :purchase do
+    #         transition all => :purchased
+    #       end
+    #       
+    #       event :restock do
+    #         transition all => :available
+    #       end
+    #       
+    #       state :available, :value => nil
+    #       state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now}
+    #     end
+    #   end
+    # 
+    # In the above definition, the <tt>:purchased</tt> state is customized with
+    # both a dynamic value *and* a value matcher.
+    # 
+    # When an object transitions to the purchased state, the value's lambda
+    # block will be called.  This will get the current time and store it in the
+    # object's +purchased_at+ attribute.
+    # 
+    # *Note* that the custom matcher is very important here.  Since there's no
+    # way for the state machine to figure out an object's state when it's set to
+    # a runtime value, it must be explicitly defined.  If the <tt>:if</tt> option
+    # were not configured for the state, then an ArgumentError exception would
+    # be raised at runtime, indicating that the state machine could not figure
+    # out what the current state of the object was.
+    # 
+    # == Behaviors
+    # 
+    # Behaviors define a series of methods to mixin with objects when the current
+    # state matches the given one(s).  This allows instance methods to behave
+    # a specific way depending on what the value of the object's state is.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     attr_accessor :driver
+    #     attr_accessor :passenger
+    #     
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #       
+    #       state :parked do
+    #         def speed
+    #           0
+    #         end
+    #         
+    #         def rotate_driver
+    #           driver = self.driver
+    #           self.driver = passenger
+    #           self.passenger = driver
+    #           true
+    #         end
+    #       end
+    #       
+    #       state :idling, :first_gear do
+    #         def speed
+    #           20
+    #         end
+    #         
+    #         def rotate_driver
+    #           self.state = 'parked'
+    #           rotate_driver
+    #         end
+    #       end
+    #       
+    #       other_states :backing_up
+    #     end
+    #   end
+    # 
+    # In the above example, there are two dynamic behaviors defined for the
+    # class:
+    # * +speed+
+    # * +rotate_driver+
+    # 
+    # Each of these behaviors are instance methods on the Vehicle class.  However,
+    # which method actually gets invoked is based on the current state of the
+    # object.  Using the above class as the example:
+    # 
+    #   vehicle = Vehicle.new
+    #   vehicle.driver = 'John'
+    #   vehicle.passenger = 'Jane'
+    #   
+    #   # Behaviors in the "parked" state
+    #   vehicle.state             # => "parked"
+    #   vehicle.speed             # => 0
+    #   vehicle.rotate_driver     # => true
+    #   vehicle.driver            # => "Jane"
+    #   vehicle.passenger         # => "John"
+    #   
+    #   vehicle.ignite            # => true
+    #   
+    #   # Behaviors in the "idling" state
+    #   vehicle.state             # => "idling"
+    #   vehicle.speed             # => 20
+    #   vehicle.rotate_driver     # => true
+    #   vehicle.driver            # => "John"
+    #   vehicle.passenger         # => "Jane"
+    # 
+    # As can be seen, both the +speed+ and +rotate_driver+ instance method
+    # implementations changed how they behave based on what the current state
+    # of the vehicle was.
+    # 
+    # === Invalid behaviors
+    # 
+    # If a specific behavior has not been defined for a state, then a
+    # NoMethodError exception will be raised, indicating that that method would
+    # not normally exist for an object with that state.
+    # 
+    # Using the example from before:
+    # 
+    #   vehicle = Vehicle.new
+    #   vehicle.state = 'backing_up'
+    #   vehicle.speed               # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
+    # 
+    # === Using matchers
+    # 
+    # The +all+ / +any+ matchers can be used to easily define behaviors for a
+    # group of states.  Note, however, that you cannot use these matchers to
+    # set configurations for states.  Behaviors using these matchers can be
+    # defined at any point in the state machine and will always get applied to
+    # the proper states.
+    # 
+    # For example:
+    # 
+    #   state_machine :initial => :parked do
+    #     ...
+    #     
+    #     state all - [:parked, :idling, :stalled] do
+    #       validates_presence_of :speed
+    #       
+    #       def speed
+    #         gear * 10
+    #       end
+    #     end
+    #   end
+    # 
+    # == State-aware class methods
+    # 
+    # In addition to defining scopes for instance methods that are state-aware,
+    # the same can be done for certain types of class methods.
+    # 
+    # Some libraries have support for class-level methods that only run certain
+    # behaviors based on a conditions hash passed in.  For example:
+    # 
+    #   class Vehicle < ActiveRecord::Base
+    #     state_machine do
+    #       ...
+    #       state :first_gear, :second_gear, :third_gear do
+    #         validates_presence_of   :speed
+    #         validates_inclusion_of  :speed, :in => 0..25, :if => :in_school_zone?
+    #       end
+    #     end
+    #   end
+    # 
+    # In the above ActiveRecord model, two validations have been defined which
+    # will *only* run when the Vehicle object is in one of the three states:
+    # +first_gear+, +second_gear+, or +third_gear.  Notice, also, that if/unless
+    # conditions can continue to be used.
+    # 
+    # This functionality is not library-specific and can work for any class-level
+    # method that is defined like so:
+    # 
+    #   def validates_presence_of(attribute, options = {})
+    #     ...
+    #   end
+    # 
+    # The minimum requirement is that the last argument in the method be an
+    # options hash which contains at least <tt>:if</tt> condition support.
+    def state(*names, &block)
+      options = names.last.is_a?(Hash) ? names.pop : {}
+      options.assert_valid_keys(:value, :cache, :if, :human_name)
+
+      # Store the context so that it can be used for / matched against any state
+      # that gets added
+      @states.context(names, &block) if block_given?
+
+      if names.first.is_a?(Matcher)
+        # Add any states referenced in the matcher.  When matchers are used,
+        # states are not allowed to be configured.
+        raise ArgumentError, "Cannot configure states when using matchers (using #{options.inspect})" if options.any?
+        states = add_states(names.first.values)
+      else
+        states = add_states(names)
+
+        # Update the configuration for the state(s)
+        states.each do |state|
+          if options.include?(:value)
+            state.value = options[:value]
+            self.states.update(state)
+          end
+
+          state.human_name = options[:human_name] if options.include?(:human_name)
+          state.cache = options[:cache] if options.include?(:cache)
+          state.matcher = options[:if] if options.include?(:if)
+        end
+      end
+
+      states.length == 1 ? states.first : states
+    end
+
+    alias_method :other_states, :state
+
+    # Gets the current value stored in the given object's attribute.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       ...
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new                           # => #<Vehicle:0xb7d94ab0 @state="parked">
+    #   Vehicle.state_machine.read(vehicle, :state)     # => "parked" # Equivalent to vehicle.state
+    #   Vehicle.state_machine.read(vehicle, :event)     # => nil      # Equivalent to vehicle.state_event
+    def read(object, attribute, ivar = false)
+      attribute = self.attribute(attribute)
+      if ivar
+        object.instance_variable_defined?("@#{attribute}") ? object.instance_variable_get("@#{attribute}") : nil
+      else
+        object.send(attribute)
+      end
+    end
+
+    # Sets a new value in the given object's attribute.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       ...
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new                                   # => #<Vehicle:0xb7d94ab0 @state="parked">
+    #   Vehicle.state_machine.write(vehicle, :state, 'idling')  # => Equivalent to vehicle.state = 'idling'
+    #   Vehicle.state_machine.write(vehicle, :event, 'park')    # => Equivalent to vehicle.state_event = 'park'
+    #   vehicle.state                                           # => "idling"
+    #   vehicle.event                                           # => "park"
+    def write(object, attribute, value, ivar = false)
+      attribute = self.attribute(attribute)
+      ivar ? object.instance_variable_set("@#{attribute}", value) : object.send("#{attribute}=", value)
+    end
+
+    # Defines one or more events for the machine and the transitions that can
+    # be performed when those events are run.
+    # 
+    # This method is also aliased as +on+ for improved compatibility with
+    # using a domain-specific language.
+    # 
+    # Configuration options:
+    # * <tt>:human_name</tt> - The human-readable version of this event's name.
+    #   By default, this is either defined by the integration or stringifies the
+    #   name and converts underscores to spaces.
+    # 
+    # == Instance methods
+    # 
+    # The following instance methods are generated when a new event is defined
+    # (the "park" event is used as an example):
+    # * <tt>park(..., run_action = true)</tt> - Fires the "park" event,
+    #   transitioning from the current state to the next valid state.  If the
+    #   last argument is a boolean, it will control whether the machine's action
+    #   gets run.
+    # * <tt>park!(..., run_action = true)</tt> - Fires the "park" event,
+    #   transitioning from the current state to the next valid state.  If the
+    #   transition fails, then a StateMachines::InvalidTransition error will be
+    #   raised.  If the last argument is a boolean, it will control whether the
+    #   machine's action gets run.
+    # * <tt>can_park?(requirements = {})</tt> - Checks whether the "park" event
+    #   can be fired given the current state of the object.  This will *not* run
+    #   validations or callbacks in ORM integrations.  It will only determine if
+    #   the state machine defines a valid transition for the event.  To check
+    #   whether an event can fire *and* passes validations, use event attributes
+    #   (e.g. state_event) as described in the "Events" documentation of each
+    #   ORM integration.
+    # * <tt>park_transition(requirements = {})</tt> -  Gets the next transition
+    #   that would be performed if the "park" event were to be fired now on the
+    #   object or nil if no transitions can be performed.  Like <tt>can_park?</tt>
+    #   this will also *not* run validations or callbacks.  It will only
+    #   determine if the state machine defines a valid transition for the event.
+    # 
+    # With a namespace of "car", the above names map to the following methods:
+    # * <tt>can_park_car?</tt>
+    # * <tt>park_car_transition</tt>
+    # * <tt>park_car</tt>
+    # * <tt>park_car!</tt>
+    # 
+    # The <tt>can_park?</tt> and <tt>park_transition</tt> helpers both take an
+    # optional set of requirements for determining what transitions are available
+    # for the current object.  These requirements include:
+    # * <tt>:from</tt> - One or more states to transition from.  If none are
+    #   specified, then this will be the object's current state.
+    # * <tt>:to</tt> - One or more states to transition to.  If none are
+    #   specified, then this will match any to state.
+    # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
+    #   conditionals defined for each one.  Default is true.
+    # 
+    # == Defining transitions
+    # 
+    # +event+ requires a block which allows you to define the possible
+    # transitions that can happen as a result of that event.  For example,
+    # 
+    #   event :park, :stop do
+    #     transition :idling => :parked
+    #   end
+    #   
+    #   event :first_gear do
+    #     transition :parked => :first_gear, :if => :seatbelt_on?
+    #     transition :parked => same # Allow to loopback if seatbelt is off
+    #   end
+    # 
+    # See StateMachines::Event#transition for more information on
+    # the possible options that can be passed in.
+    # 
+    # *Note* that this block is executed within the context of the actual event
+    # object.  As a result, you will not be able to reference any class methods
+    # on the model without referencing the class itself.  For example,
+    # 
+    #   class Vehicle
+    #     def self.safe_states
+    #       [:parked, :idling, :stalled]
+    #     end
+    #     
+    #     state_machine do
+    #       event :park do
+    #         transition Vehicle.safe_states => :parked
+    #       end
+    #     end
+    #   end 
+    # 
+    # == Overriding the event method
+    # 
+    # By default, this will define an instance method (with the same name as the
+    # event) that will fire the next possible transition for that.  Although the
+    # +before_transition+, +after_transition+, and +around_transition+ hooks
+    # allow you to define behavior that gets executed as a result of the event's
+    # transition, you can also override the event method in order to have a
+    # little more fine-grained control.
+    # 
+    # For example:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       event :park do
+    #         ...
+    #       end
+    #     end
+    #     
+    #     def park(*)
+    #       take_deep_breath  # Executes before the transition (and before_transition hooks) even if no transition is possible
+    #       if result = super # Runs the transition and all before/after/around hooks
+    #         applaud         # Executes after the transition (and after_transition hooks)
+    #       end
+    #       result
+    #     end
+    #   end
+    # 
+    # There are a few important things to note here.  First, the method
+    # signature is defined with an unlimited argument list in order to allow
+    # callers to continue passing arguments that are expected by state_machine.
+    # For example, it will still allow calls to +park+ with a single parameter
+    # for skipping the configured action.
+    # 
+    # Second, the overridden event method must call +super+ in order to run the
+    # logic for running the next possible transition.  In order to remain
+    # consistent with other events, the result of +super+ is returned.
+    # 
+    # Third, any behavior defined in this method will *not* get executed if
+    # you're taking advantage of attribute-based event transitions.  For example:
+    # 
+    #   vehicle = Vehicle.new
+    #   vehicle.state_event = 'park'
+    #   vehicle.save
+    # 
+    # In this case, the +park+ event will run the before/after/around transition
+    # hooks and transition the state, but the behavior defined in the overriden
+    # +park+ method will *not* be executed.
+    # 
+    # == Defining additional arguments
+    # 
+    # Additional arguments can be passed into events and accessed by transition
+    # hooks like so:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       after_transition :on => :park do |vehicle, transition|
+    #         kind = *transition.args # :parallel
+    #         ...
+    #       end
+    #       after_transition :on => :park, :do => :take_deep_breath
+    #       
+    #       event :park do
+    #         ...
+    #       end
+    #       
+    #       def take_deep_breath(transition)
+    #         kind = *transition.args # :parallel
+    #         ...
+    #       end
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new
+    #   vehicle.park(:parallel)
+    # 
+    # *Remember* that if the last argument is a boolean, it will be used as the
+    # +run_action+ parameter to the event action.  Using the +park+ action
+    # example from above, you can might call it like so:
+    # 
+    #   vehicle.park                    # => Uses default args and runs machine action
+    #   vehicle.park(:parallel)         # => Specifies the +kind+ argument and runs the machine action
+    #   vehicle.park(:parallel, false)  # => Specifies the +kind+ argument and *skips* the machine action
+    # 
+    # If you decide to override the +park+ event method *and* define additional
+    # arguments, you can do so as shown below:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       event :park do
+    #         ...
+    #       end
+    #     end
+    #     
+    #     def park(kind = :parallel, *args)
+    #       take_deep_breath if kind == :parallel
+    #       super
+    #     end
+    #   end
+    # 
+    # Note that +super+ is called instead of <tt>super(*args)</tt>.  This allow
+    # the entire arguments list to be accessed by transition callbacks through
+    # StateMachines::Transition#args.
+    # 
+    # === Using matchers
+    # 
+    # The +all+ / +any+ matchers can be used to easily execute blocks for a
+    # group of events.  Note, however, that you cannot use these matchers to
+    # set configurations for events.  Blocks using these matchers can be
+    # defined at any point in the state machine and will always get applied to
+    # the proper events.
+    # 
+    # For example:
+    # 
+    #   state_machine :initial => :parked do
+    #     ...
+    #     
+    #     event all - [:crash] do
+    #       transition :stalled => :parked
+    #     end
+    #   end
+    # 
+    # == Example
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       # The park, stop, and halt events will all share the given transitions
+    #       event :park, :stop, :halt do
+    #         transition [:idling, :backing_up] => :parked
+    #       end
+    #       
+    #       event :stop do
+    #         transition :first_gear => :idling
+    #       end
+    #       
+    #       event :ignite do
+    #         transition :parked => :idling
+    #         transition :idling => same # Allow ignite while still idling
+    #       end
+    #     end
+    #   end
+    def event(*names, &block)
+      options = names.last.is_a?(Hash) ? names.pop : {}
+      options.assert_valid_keys(:human_name)
+
+      # Store the context so that it can be used for / matched against any event
+      # that gets added
+      @events.context(names, &block) if block_given?
+
+      if names.first.is_a?(Matcher)
+        # Add any events referenced in the matcher.  When matchers are used,
+        # events are not allowed to be configured.
+        raise ArgumentError, "Cannot configure events when using matchers (using #{options.inspect})" if options.any?
+        events = add_events(names.first.values)
+      else
+        events = add_events(names)
+
+        # Update the configuration for the event(s)
+        events.each do |event|
+          event.human_name = options[:human_name] if options.include?(:human_name)
+
+          # Add any states that may have been referenced within the event
+          add_states(event.known_states)
+        end
+      end
+
+      events.length == 1 ? events.first : events
+    end
+
+    alias_method :on, :event
+
+    # Creates a new transition that determines what to change the current state
+    # to when an event fires.
+    # 
+    # == Defining transitions
+    # 
+    # The options for a new transition uses the Hash syntax to map beginning
+    # states to ending states.  For example,
+    # 
+    #   transition :parked => :idling, :idling => :first_gear, :on => :ignite
+    # 
+    # In this case, when the +ignite+ event is fired, this transition will cause
+    # the state to be +idling+ if it's current state is +parked+ or +first_gear+
+    # if it's current state is +idling+.
+    # 
+    # To help define these implicit transitions, a set of helpers are available
+    # for slightly more complex matching:
+    # * <tt>all</tt> - Matches every state in the machine
+    # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state except those specified
+    # * <tt>any</tt> - An alias for +all+ (matches every state in the machine)
+    # * <tt>same</tt> - Matches the same state being transitioned from
+    # 
+    # See StateMachines::MatcherHelpers for more information.
+    # 
+    # Examples:
+    # 
+    #   transition all => nil, :on => :ignite                               # Transitions to nil regardless of the current state
+    #   transition all => :idling, :on => :ignite                           # Transitions to :idling regardless of the current state
+    #   transition all - [:idling, :first_gear] => :idling, :on => :ignite  # Transitions every state but :idling and :first_gear to :idling
+    #   transition nil => :idling, :on => :ignite                           # Transitions to :idling from the nil state
+    #   transition :parked => :idling, :on => :ignite                       # Transitions to :idling if :parked
+    #   transition [:parked, :stalled] => :idling, :on => :ignite           # Transitions to :idling if :parked or :stalled
+    #   
+    #   transition :parked => same, :on => :park                            # Loops :parked back to :parked
+    #   transition [:parked, :stalled] => same, :on => [:park, :stall]      # Loops either :parked or :stalled back to the same state on the park and stall events
+    #   transition all - :parked => same, :on => :noop                      # Loops every state but :parked back to the same state
+    #   
+    #   # Transitions to :idling if :parked, :first_gear if :idling, or :second_gear if :first_gear
+    #   transition :parked => :idling, :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up
+    # 
+    # == Verbose transitions
+    # 
+    # Transitions can also be defined use an explicit set of configuration
+    # options:
+    # * <tt>:from</tt> - A state or array of states that can be transitioned from.
+    #   If not specified, then the transition can occur for *any* state.
+    # * <tt>:to</tt> - The state that's being transitioned to.  If not specified,
+    #   then the transition will simply loop back (i.e. the state will not change).
+    # * <tt>:except_from</tt> - A state or array of states that *cannot* be
+    #   transitioned from.
+    # 
+    # These options must be used when defining transitions within the context
+    # of a state.
+    # 
+    # Examples:
+    # 
+    #   transition :to => nil, :on => :park
+    #   transition :to => :idling, :on => :ignite
+    #   transition :except_from => [:idling, :first_gear], :to => :idling, :on => :ignite
+    #   transition :from => nil, :to => :idling, :on => :ignite
+    #   transition :from => [:parked, :stalled], :to => :idling, :on => :ignite
+    #   
+    # == Conditions
+    # 
+    # In addition to the state requirements for each transition, a condition
+    # can also be defined to help determine whether that transition is
+    # available.  These options will work on both the normal and verbose syntax.
+    # 
+    # Configuration options:
+    # * <tt>:if</tt> - A method, proc or string to call to determine if the
+    #   transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}).
+    #   The condition should return or evaluate to true or false.
+    # * <tt>:unless</tt> - A method, proc or string to call to determine if the
+    #   transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}).
+    #   The condition should return or evaluate to true or false.
+    # 
+    # Examples:
+    # 
+    #   transition :parked => :idling, :on => :ignite, :if => :moving?
+    #   transition :parked => :idling, :on => :ignite, :unless => :stopped?
+    #   transition :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up, :if => :seatbelt_on?
+    #   
+    #   transition :from => :parked, :to => :idling, :on => ignite, :if => :moving?
+    #   transition :from => :parked, :to => :idling, :on => ignite, :unless => :stopped?
+    # 
+    # == Order of operations
+    # 
+    # Transitions are evaluated in the order in which they're defined.  As a
+    # result, if more than one transition applies to a given object, then the
+    # first transition that matches will be performed.
+    def transition(options)
+      raise ArgumentError, 'Must specify :on event' unless options[:on]
+
+      branches = []
+      options = options.dup
+      event(*Array(options.delete(:on))) { branches << transition(options) }
+
+      branches.length == 1 ? branches.first : branches
+    end
+
+    # Creates a callback that will be invoked *before* a transition is
+    # performed so long as the given requirements match the transition.
+    # 
+    # == The callback
+    # 
+    # Callbacks must be defined as either an argument, in the :do option, or
+    # as a block.  For example,
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       before_transition :set_alarm
+    #       before_transition :set_alarm, all => :parked
+    #       before_transition all => :parked, :do => :set_alarm
+    #       before_transition all => :parked do |vehicle, transition|
+    #         vehicle.set_alarm
+    #       end
+    #       ...
+    #     end
+    #   end
+    # 
+    # Notice that the first three callbacks are the same in terms of how the
+    # methods to invoke are defined.  However, using the <tt>:do</tt> can
+    # provide for a more fluid DSL.
+    # 
+    # In addition, multiple callbacks can be defined like so:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       before_transition :set_alarm, :lock_doors, all => :parked
+    #       before_transition all => :parked, :do => [:set_alarm, :lock_doors]
+    #       before_transition :set_alarm do |vehicle, transition|
+    #         vehicle.lock_doors
+    #       end
+    #     end
+    #   end
+    # 
+    # Notice that the different ways of configuring methods can be mixed.
+    # 
+    # == State requirements
+    # 
+    # Callbacks can require that the machine be transitioning from and to
+    # specific states.  These requirements use a Hash syntax to map beginning
+    # states to ending states.  For example,
+    # 
+    #   before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm
+    # 
+    # In this case, the +set_alarm+ callback will only be called if the machine
+    # is transitioning from +parked+ to +idling+ or from +idling+ to +parked+.
+    # 
+    # To help define state requirements, a set of helpers are available for
+    # slightly more complex matching:
+    # * <tt>all</tt> - Matches every state/event in the machine
+    # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state/event except those specified
+    # * <tt>any</tt> - An alias for +all+ (matches every state/event in the machine)
+    # * <tt>same</tt> - Matches the same state being transitioned from
+    # 
+    # See StateMachines::MatcherHelpers for more information.
+    # 
+    # Examples:
+    # 
+    #   before_transition :parked => [:idling, :first_gear], :do => ...     # Matches from parked to idling or first_gear
+    #   before_transition all - [:parked, :idling] => :idling, :do => ...   # Matches from every state except parked and idling to idling
+    #   before_transition all => :parked, :do => ...                        # Matches all states to parked
+    #   before_transition any => same, :do => ...                           # Matches every loopback
+    # 
+    # == Event requirements
+    # 
+    # In addition to state requirements, an event requirement can be defined so
+    # that the callback is only invoked on specific events using the +on+
+    # option.  This can also use the same matcher helpers as the state
+    # requirements.
+    # 
+    # Examples:
+    # 
+    #   before_transition :on => :ignite, :do => ...                        # Matches only on ignite
+    #   before_transition :on => all - :ignite, :do => ...                  # Matches on every event except ignite
+    #   before_transition :parked => :idling, :on => :ignite, :do => ...    # Matches from parked to idling on ignite
+    # 
+    # == Verbose Requirements
+    # 
+    # Requirements can also be defined using verbose options rather than the
+    # implicit Hash syntax and helper methods described above.
+    # 
+    # Configuration options:
+    # * <tt>:from</tt> - One or more states being transitioned from.  If none
+    #   are specified, then all states will match.
+    # * <tt>:to</tt> - One or more states being transitioned to.  If none are
+    #   specified, then all states will match.
+    # * <tt>:on</tt> - One or more events that fired the transition.  If none
+    #   are specified, then all events will match.
+    # * <tt>:except_from</tt> - One or more states *not* being transitioned from
+    # * <tt>:except_to</tt> - One more states *not* being transitioned to
+    # * <tt>:except_on</tt> - One or more events that *did not* fire the transition
+    # 
+    # Examples:
+    # 
+    #   before_transition :from => :ignite, :to => :idling, :on => :park, :do => ...
+    #   before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...
+    # 
+    # == Conditions
+    # 
+    # In addition to the state/event requirements, a condition can also be
+    # defined to help determine whether the callback should be invoked.
+    # 
+    # Configuration options:
+    # * <tt>:if</tt> - A method, proc or string to call to determine if the
+    #   callback should occur (e.g. :if => :allow_callbacks, or
+    #   :if => lambda {|user| user.signup_step > 2}). The method, proc or string
+    #   should return or evaluate to a true or false value. 
+    # * <tt>:unless</tt> - A method, proc or string to call to determine if the
+    #   callback should not occur (e.g. :unless => :skip_callbacks, or
+    #   :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
+    #   string should return or evaluate to a true or false value. 
+    # 
+    # Examples:
+    # 
+    #   before_transition :parked => :idling, :if => :moving?, :do => ...
+    #   before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...
+    # 
+    # == Accessing the transition
+    # 
+    # In addition to passing the object being transitioned, the actual
+    # transition describing the context (e.g. event, from, to) can be accessed
+    # as well.  This additional argument is only passed if the callback allows
+    # for it.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     # Only specifies one parameter (the object being transitioned)
+    #     before_transition all => :parked do |vehicle|
+    #       vehicle.set_alarm
+    #     end
+    #     
+    #     # Specifies 2 parameters (object being transitioned and actual transition)
+    #     before_transition all => :parked do |vehicle, transition|
+    #       vehicle.set_alarm(transition)
+    #     end
+    #   end
+    # 
+    # *Note* that the object in the callback will only be passed in as an
+    # argument if callbacks are configured to *not* be bound to the object
+    # involved.  This is the default and may change on a per-integration basis.
+    # 
+    # See StateMachines::Transition for more information about the
+    # attributes available on the transition.
+    # 
+    # == Usage with delegates
+    # 
+    # As noted above, state_machine uses the callback method's argument list
+    # arity to determine whether to include the transition in the method call.
+    # If you're using delegates, such as those defined in ActiveSupport or
+    # Forwardable, the actual arity of the delegated method gets masked.  This
+    # means that callbacks which reference delegates will always get passed the
+    # transition as an argument.  For example:
+    # 
+    #   class Vehicle
+    #     extend Forwardable
+    #     delegate :refresh => :dashboard
+    #     
+    #     state_machine do
+    #       before_transition :refresh
+    #       ...
+    #     end
+    #     
+    #     def dashboard
+    #       @dashboard ||= Dashboard.new
+    #     end
+    #   end
+    #   
+    #   class Dashboard
+    #     def refresh(transition)
+    #       # ...
+    #     end
+    #   end
+    # 
+    # In the above example, <tt>Dashboard#refresh</tt> *must* defined a
+    # +transition+ argument.  Otherwise, an +ArgumentError+ exception will get
+    # raised.  The only way around this is to avoid the use of delegates and
+    # manually define the delegate method so that the correct arity is used.
+    # 
+    # == Examples
+    # 
+    # Below is an example of a class with one state machine and various types
+    # of +before+ transitions defined for it:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       # Before all transitions
+    #       before_transition :update_dashboard
+    #       
+    #       # Before specific transition:
+    #       before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt
+    #       
+    #       # With conditional callback:
+    #       before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
+    #       
+    #       # Using helpers:
+    #       before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
+    #       ...
+    #     end
+    #   end
+    # 
+    # As can be seen, any number of transitions can be created using various
+    # combinations of configuration options.
+    def before_transition(*args, &block)
+      options = (args.last.is_a?(Hash) ? args.pop : {})
+      options[:do] = args if args.any?
+      add_callback(:before, options, &block)
+    end
+
+    # Creates a callback that will be invoked *after* a transition is
+    # performed so long as the given requirements match the transition.
+    # 
+    # See +before_transition+ for a description of the possible configurations
+    # for defining callbacks.
+    def after_transition(*args, &block)
+      options = (args.last.is_a?(Hash) ? args.pop : {})
+      options[:do] = args if args.any?
+      add_callback(:after, options, &block)
+    end
+
+    # Creates a callback that will be invoked *around* a transition so long as
+    # the given requirements match the transition.
+    # 
+    # == The callback
+    # 
+    # Around callbacks wrap transitions, executing code both before and after.
+    # These callbacks are defined in the exact same manner as before / after
+    # callbacks with the exception that the transition must be yielded to in
+    # order to finish running it.
+    # 
+    # If defining +around+ callbacks using blocks, you must yield within the
+    # transition by directly calling the block (since yielding is not allowed
+    # within blocks).
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       around_transition do |block|
+    #         Benchmark.measure { block.call }
+    #       end
+    #       
+    #       around_transition do |vehicle, block|
+    #         logger.info "vehicle was #{state}..."
+    #         block.call
+    #         logger.info "...and is now #{state}"
+    #       end
+    #       
+    #       around_transition do |vehicle, transition, block|
+    #         logger.info "before #{transition.event}: #{vehicle.state}"
+    #         block.call
+    #         logger.info "after #{transition.event}: #{vehicle.state}"
+    #       end
+    #     end
+    #   end
+    # 
+    # Notice that referencing the block is similar to doing so within an
+    # actual method definition in that it is always the last argument.
+    # 
+    # On the other hand, if you're defining +around+ callbacks using method
+    # references, you can yield like normal:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       around_transition :benchmark
+    #       ...
+    #     end
+    #     
+    #     def benchmark
+    #       Benchmark.measure { yield }
+    #     end
+    #   end
+    # 
+    # See +before_transition+ for a description of the possible configurations
+    # for defining callbacks.
+    def around_transition(*args, &block)
+      options = (args.last.is_a?(Hash) ? args.pop : {})
+      options[:do] = args if args.any?
+      add_callback(:around, options, &block)
+    end
+
+    # Creates a callback that will be invoked *after* a transition failures to
+    # be performed so long as the given requirements match the transition.
+    # 
+    # See +before_transition+ for a description of the possible configurations
+    # for defining callbacks.  *Note* however that you cannot define the state
+    # requirements in these callbacks.  You may only define event requirements.
+    # 
+    # = The callback
+    # 
+    # Failure callbacks get invoked whenever an event fails to execute.  This
+    # can happen when no transition is available, a +before+ callback halts
+    # execution, or the action associated with this machine fails to succeed.
+    # In any of these cases, any failure callback that matches the attempted
+    # transition will be run.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       after_failure do |vehicle, transition|
+    #         logger.error "vehicle #{vehicle} failed to transition on #{transition.event}"
+    #       end
+    #       
+    #       after_failure :on => :ignite, :do => :log_ignition_failure
+    #       
+    #       ...
+    #     end
+    #   end
+    def after_failure(*args, &block)
+      options = (args.last.is_a?(Hash) ? args.pop : {})
+      options[:do] = args if args.any?
+      options.assert_valid_keys(:on, :do, :if, :unless)
+
+      add_callback(:failure, options, &block)
+    end
+
+    # Generates a list of the possible transition sequences that can be run on
+    # the given object.  These paths can reveal all of the possible states and
+    # events that can be encountered in the object's state machine based on the
+    # object's current state.
+    # 
+    # Configuration options:
+    # * +from+ - The initial state to start all paths from.  By default, this
+    #   is the object's current state.
+    # * +to+ - The target state to end all paths on.  By default, paths will
+    #   end when they loop back to the first transition on the path.
+    # * +deep+ - Whether to allow the target state to be crossed more than once
+    #   in a path.  By default, paths will immediately stop when the target
+    #   state (if specified) is reached.  If this is enabled, then paths can
+    #   continue even after reaching the target state; they will stop when
+    #   reaching the target state a second time.
+    # 
+    # *Note* that the object is never modified when the list of paths is
+    # generated.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #       
+    #       event :shift_up do
+    #         transition :idling => :first_gear, :first_gear => :second_gear
+    #       end
+    #       
+    #       event :shift_down do
+    #         transition :second_gear => :first_gear, :first_gear => :idling
+    #       end
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new   # => #<Vehicle:0xb7c27024 @state="parked">
+    #   vehicle.state           # => "parked"
+    #   
+    #   vehicle.state_paths
+    #   # => [
+    #   #     [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_down from="second_gear" from_name=:second_gear to="first_gear" to_name=:first_gear>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>],
+    #   #       
+    #   #     [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>]
+    #   #    ]
+    #   
+    #   vehicle.state_paths(:from => :parked, :to => :second_gear)
+    #   # => [
+    #   #     [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
+    #   #      #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>]
+    #   #    ]
+    # 
+    # In addition to getting the possible paths that can be accessed, you can
+    # also get summary information about the states / events that can be
+    # accessed at some point along one of the paths.  For example:
+    # 
+    #   # Get the list of states that can be accessed from the current state
+    #   vehicle.state_paths.to_states # => [:idling, :first_gear, :second_gear]
+    #   
+    #   # Get the list of events that can be accessed from the current state
+    #   vehicle.state_paths.events    # => [:ignite, :shift_up, :shift_down]
+    def paths_for(object, requirements = {})
+      PathCollection.new(object, self, requirements)
+    end
+
+    # Marks the given object as invalid with the given message.
+    # 
+    # By default, this is a no-op.
+    def invalidate(_object, _attribute, _message, _values = [])
+    end
+
+    # Gets a description of the errors for the given object.  This is used to
+    # provide more detailed information when an InvalidTransition exception is
+    # raised.
+    def errors_for(_object)
+      ''
+    end
+
+    # Resets any errors previously added when invalidating the given object.
+    # 
+    # By default, this is a no-op.
+    def reset(_object)
+    end
+
+    # Generates the message to use when invalidating the given object after
+    # failing to transition on a specific event
+    def generate_message(name, values = [])
+      message = (@messages[name] || self.class.default_messages[name])
+
+      # Check whether there are actually any values to interpolate to avoid
+      # any warnings
+      if message.scan(/%./).any? { |match| match != '%%' }
+        message % values.map { |value| value.last }
+      else
+        message
+      end
+    end
+
+    # Runs a transaction, rolling back any changes if the yielded block fails.
+    # 
+    # This is only applicable to integrations that involve databases.  By
+    # default, this will not run any transactions since the changes aren't
+    # taking place within the context of a database.
+    def within_transaction(object)
+      if use_transactions
+        transaction(object) { yield }
+      else
+        yield
+      end
+    end
+
+
+    def draw(*)
+      fail NotImplementedError
+    end
+
+    # Determines whether an action hook was defined for firing attribute-based
+    # event transitions when the configured action gets called.
+    def action_hook?(self_only = false)
+      @action_hook_defined || !self_only && owner_class.state_machines.any? { |name, machine| machine.action == action && machine != self && machine.action_hook?(true) }
+    end
+
+    protected
+    # Runs additional initialization hooks.  By default, this is a no-op.
+    def after_initialize
+    end
+
+    # Looks up other machines that have been defined in the owner class and
+    # are targeting the same attribute as this machine.  When accessing
+    # sibling machines, they will be automatically copied for the current
+    # class if they haven't been already.  This ensures that any configuration
+    # changes made to the sibling machines only affect this class and not any
+    # base class that may have originally defined the machine.
+    def sibling_machines
+      owner_class.state_machines.inject([]) do |machines, (name, machine)|
+        if machine.attribute == attribute && machine != self
+          machines << (owner_class.state_machine(name) {})
+        end
+        machines
+      end
+    end
+
+    # Determines if the machine's attribute needs to be initialized.  This
+    # will only be true if the machine's attribute is blank.
+    def initialize_state?(object)
+      value = read(object, :state)
+      (value.nil? || value.respond_to?(:empty?) && value.empty?) && !states[value, :value]
+    end
+
+    # Adds helper methods for interacting with the state machine, including
+    # for states, events, and transitions
+    def define_helpers
+      define_state_accessor
+      define_state_predicate
+      define_event_helpers
+      define_path_helpers
+      define_action_helpers if define_action_helpers?
+      define_name_helpers
+    end
+
+    # Defines the initial values for state machine attributes.  Static values
+    # are set prior to the original initialize method and dynamic values are
+    # set *after* the initialize method in case it is dependent on it.
+    def define_state_initializer
+      define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+          def initialize(*)
+            self.class.state_machines.initialize_states(self) { super }
+          end
+      end_eval
+    end
+
+    # Adds reader/writer methods for accessing the state attribute
+    def define_state_accessor
+      attribute = self.attribute
+
+      @helper_modules[:instance].class_eval { attr_reader attribute } unless owner_class_ancestor_has_method?(:instance, attribute)
+      @helper_modules[:instance].class_eval { attr_writer attribute } unless owner_class_ancestor_has_method?(:instance, "#{attribute}=")
+    end
+
+    # Adds predicate method to the owner class for determining the name of the
+    # current state
+    def define_state_predicate
+      call_super = !!owner_class_ancestor_has_method?(:instance, "#{name}?")
+      define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+          def #{name}?(*args)
+            args.empty? && (#{call_super} || defined?(super)) ? super : self.class.state_machine(#{name.inspect}).states.matches?(self, *args)
+          end
+      end_eval
+    end
+
+    # Adds helper methods for getting information about this state machine's
+    # events
+    def define_event_helpers
+      # Gets the events that are allowed to fire on the current object
+      define_helper(:instance, attribute(:events)) do |machine, object, *args|
+        machine.events.valid_for(object, *args).map { |event| event.name }
+      end
+
+      # Gets the next possible transitions that can be run on the current
+      # object
+      define_helper(:instance, attribute(:transitions)) do |machine, object, *args|
+        machine.events.transitions_for(object, *args)
+      end
+
+      # Fire an arbitrary event for this machine
+      define_helper(:instance, "fire_#{attribute(:event)}") do |machine, object, event, *args|
+        machine.events.fetch(event).fire(object, *args)
+      end
+
+      # Add helpers for tracking the event / transition to invoke when the
+      # action is called
+      if action
+        event_attribute = attribute(:event)
+        define_helper(:instance, event_attribute) do |machine, object|
+          # Interpret non-blank events as present
+          event = machine.read(object, :event, true)
+          event && !(event.respond_to?(:empty?) && event.empty?) ? event.to_sym : nil
+        end
+
+        # A roundabout way of writing the attribute is used here so that
+        # integrations can hook into this modification
+        define_helper(:instance, "#{event_attribute}=") do |machine, object, value|
+          machine.write(object, :event, value, true)
+        end
+
+        event_transition_attribute = attribute(:event_transition)
+        define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+            protected; attr_accessor #{event_transition_attribute.inspect}
+        end_eval
+      end
+    end
+
+    # Adds helper methods for getting information about this state machine's
+    # available transition paths
+    def define_path_helpers
+      # Gets the paths of transitions available to the current object
+      define_helper(:instance, attribute(:paths)) do |machine, object, *args|
+        machine.paths_for(object, *args)
+      end
+    end
+
+    # Determines whether action helpers should be defined for this machine.
+    # This is only true if there is an action configured and no other machines
+    # have process this same configuration already.
+    def define_action_helpers?
+      action && !owner_class.state_machines.any? { |name, machine| machine.action == action && machine != self }
+    end
+
+    # Adds helper methods for automatically firing events when an action
+    # is invoked
+    def define_action_helpers
+      if action_hook
+        @action_hook_defined = true
+        define_action_hook
+      end
+    end
+
+    # Hooks directly into actions by defining the same method in an included
+    # module.  As a result, when the action gets invoked, any state events
+    # defined for the object will get run.  Method visibility is preserved.
+    def define_action_hook
+      action_hook = self.action_hook
+      action = self.action
+      private_action_hook = owner_class.private_method_defined?(action_hook)
+
+      # Only define helper if it hasn't
+      define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+          def #{action_hook}(*)
+            self.class.state_machines.transitions(self, #{action.inspect}).perform { super }
+          end
+          
+          private #{action_hook.inspect} if #{private_action_hook}
+      end_eval
+    end
+
+    # The method to hook into for triggering transitions when invoked.  By
+    # default, this is the action configured for the machine.
+    #
+    # Since the default hook technique relies on module inheritance, the
+    # action must be defined in an ancestor of the owner classs in order for
+    # it to be the action hook.
+    def action_hook
+      action && owner_class_ancestor_has_method?(:instance, action) ? action : nil
+    end
+
+    # Determines whether there's already a helper method defined within the
+    # given scope.  This is true only if one of the owner's ancestors defines
+    # the method and is further along in the ancestor chain than this
+    # machine's helper module.
+    def owner_class_ancestor_has_method?(scope, method)
+      superclasses = owner_class.ancestors[1..-1].select { |ancestor| ancestor.is_a?(Class) }
+
+      if scope == :class
+        # Use singleton classes
+        current = (
+        class << owner_class;
+          self;
+        end)
+        superclass = superclasses.first
+      else
+        current = owner_class
+        superclass = owner_class.superclass
+      end
+
+      # Generate the list of modules that *only* occur in the owner class, but
+      # were included *prior* to the helper modules, in addition to the
+      # superclasses
+      ancestors = current.ancestors - superclass.ancestors + superclasses
+      ancestors = ancestors[ancestors.index(@helper_modules[scope])..-1].reverse
+
+      # Search for for the first ancestor that defined this method
+      ancestors.detect do |ancestor|
+        ancestor = (
+        class << ancestor;
+          self;
+        end) if scope == :class && ancestor.is_a?(Class)
+        ancestor.method_defined?(method) || ancestor.private_method_defined?(method)
+      end
+    end
+
+    # Adds helper methods for accessing naming information about states and
+    # events on the owner class
+    def define_name_helpers
+      # Gets the humanized version of a state
+      define_helper(:class, "human_#{attribute(:name)}") do |machine, klass, state|
+        machine.states.fetch(state).human_name(klass)
+      end
+
+      # Gets the humanized version of an event
+      define_helper(:class, "human_#{attribute(:event_name)}") do |machine, klass, event|
+        machine.events.fetch(event).human_name(klass)
+      end
+
+      # Gets the state name for the current value
+      define_helper(:instance, attribute(:name)) do |machine, object|
+        machine.states.match!(object).name
+      end
+
+      # Gets the human state name for the current value
+      define_helper(:instance, "human_#{attribute(:name)}") do |machine, object|
+        machine.states.match!(object).human_name(object.class)
+      end
+    end
+
+    # Defines the with/without scope helpers for this attribute.  Both the
+    # singular and plural versions of the attribute are defined for each
+    # scope helper.  A custom plural can be specified if it cannot be
+    # automatically determined by either calling +pluralize+ on the attribute
+    # name or adding an "s" to the end of the name.
+    def define_scopes(custom_plural = nil)
+      plural = custom_plural || pluralize(name)
+
+      [:with, :without].each do |kind|
+        [name, plural].map { |s| s.to_s }.uniq.each do |suffix|
+          method = "#{kind}_#{suffix}"
+
+          if scope = send("create_#{kind}_scope", method)
+            # Converts state names to their corresponding values so that they
+            # can be looked up properly
+            define_helper(:class, method) do |machine, klass, *states|
+              run_scope(scope, machine, klass, states)
+            end
+          end
+        end
+      end
+    end
+
+    # Generates the results for the given scope based on one or more states to
+    # filter by
+    def run_scope(scope, machine, klass, states)
+      values = states.flatten.map { |state| machine.states.fetch(state).value }
+      scope.call(klass, values)
+    end
+
+    # Pluralizes the given word using #pluralize (if available) or simply
+    # adding an "s" to the end of the word
+    def pluralize(word)
+      word = word.to_s
+      if word.respond_to?(:pluralize)
+        word.pluralize
+      else
+        "#{name}s"
+      end
+    end
+
+    # Creates a scope for finding objects *with* a particular value or values
+    # for the attribute.
+    #
+    # By default, this is a no-op.
+    def create_with_scope(name)
+    end
+
+    # Creates a scope for finding objects *without* a particular value or
+    # values for the attribute.
+    #
+    # By default, this is a no-op.
+    def create_without_scope(name)
+    end
+
+    # Always yields
+    def transaction(object)
+      yield
+    end
+
+    # Gets the initial attribute value defined by the owner class (outside of
+    # the machine's definition). By default, this is always nil.
+    def owner_class_attribute_default
+      nil
+    end
+
+    # Checks whether the given state matches the attribute default specified
+    # by the owner class
+    def owner_class_attribute_default_matches?(state)
+      state.matches?(owner_class_attribute_default)
+    end
+
+    # Updates this machine based on the configuration of other machines in the
+    # owner class that share the same target attribute.
+    def add_sibling_machine_configs
+      # Add existing states
+      sibling_machines.each do |machine|
+        machine.states.each { |state| states << state unless states[state.name] }
+      end
+    end
+
+    # Adds a new transition callback of the given type.
+    def add_callback(type, options, &block)
+      callbacks[type == :around ? :before : type] << callback = Callback.new(type, options, &block)
+      add_states(callback.known_states)
+      callback
+    end
+
+    # Tracks the given set of states in the list of all known states for
+    # this machine
+    def add_states(new_states)
+      new_states.map do |new_state|
+        # Check for other states that use a different class type for their name.
+        # This typically prevents string / symbol misuse.
+        if new_state && conflict = states.detect { |state| state.name && state.name.class != new_state.class }
+          raise ArgumentError, "#{new_state.inspect} state defined as #{new_state.class}, #{conflict.name.inspect} defined as #{conflict.name.class}; all states must be consistent"
+        end
+
+        unless state = states[new_state]
+          states << state = State.new(self, new_state)
+
+          # Copy states over to sibling machines
+          sibling_machines.each { |machine| machine.states << state }
+        end
+
+        state
+      end
+    end
+
+    # Tracks the given set of events in the list of all known events for
+    # this machine
+    def add_events(new_events)
+      new_events.map do |new_event|
+        # Check for other states that use a different class type for their name.
+        # This typically prevents string / symbol misuse.
+        if conflict = events.detect { |event| event.name.class != new_event.class }
+          raise ArgumentError, "#{new_event.inspect} event defined as #{new_event.class}, #{conflict.name.inspect} defined as #{conflict.name.class}; all events must be consistent"
+        end
+
+        unless event = events[new_event]
+          events << event = Event.new(self, new_event)
+        end
+
+        event
+      end
+    end
+  end
+end
diff --git a/lib/state_machines/machine_collection.rb b/lib/state_machines/machine_collection.rb
new file mode 100644
index 0000000..a301aaa
--- /dev/null
+++ b/lib/state_machines/machine_collection.rb
@@ -0,0 +1,96 @@
+module StateMachines
+  # Represents a collection of state machines for a class
+  class MachineCollection < Hash
+    # Initializes the state of each machine in the given object.  This can allow
+    # states to be initialized in two groups: static and dynamic.  For example:
+    # 
+    #   machines.initialize_states(object) do
+    #     # After static state initialization, before dynamic state initialization
+    #   end
+    # 
+    # If no block is provided, then all states will still be initialized.
+    # 
+    # Valid configuration options:
+    # * <tt>:static</tt> - Whether to initialize static states. Unless set to
+    #   false, the state will be initialized regardless of its current value.
+    #   Default is true.
+    # * <tt>:dynamic</tt> - Whether to initialize dynamic states.  If set to
+    #   :force, the state will be initialized regardless of its current value.
+    #   Default is true.
+    # * <tt>:to</tt> - A hash to write the initialized state to instead of
+    #   writing to the object.  Default is to write directly to the object.
+    def initialize_states(object, options = {}, attributes = {})
+      options.assert_valid_keys( :static, :dynamic, :to)
+      options = {:static => true, :dynamic => true}.merge(options)
+
+      result = yield if block_given?
+
+      each_value do |machine|
+        unless machine.dynamic_initial_state?
+          force = options[:static] == :force || !attributes.keys.map(&:to_sym).include?(machine.attribute)
+          machine.initialize_state(object, force: force, :to => options[:to])
+        end
+      end if options[:static]
+
+      each_value do |machine|
+        machine.initialize_state(object, :force => options[:dynamic] == :force, :to => options[:to]) if machine.dynamic_initial_state?
+      end if options[:dynamic]
+
+      result
+    end
+
+    # Runs one or more events in parallel on the given object.  See
+    # StateMachines::InstanceMethods#fire_events for more information.
+    def fire_events(object, *events)
+      run_action = [true, false].include?(events.last) ? events.pop : true
+
+      # Generate the transitions to run for each event
+      transitions = events.collect do |event_name|
+        # Find the actual event being run
+        event = nil
+        detect {|name, machine| event = machine.events[event_name, :qualified_name]}
+
+        raise(InvalidEvent.new(object, event_name)) unless event
+
+        # Get the transition that will be performed for the event
+        unless transition = event.transition_for(object)
+          event.on_failure(object)
+        end
+        transition
+      end.compact
+
+      # Run the events in parallel only if valid transitions were found for
+      # all of them
+      if events.length == transitions.length
+        TransitionCollection.new(transitions, {use_transactions: resolve_use_transactions, actions: run_action}).perform
+      else
+        false
+      end
+    end
+
+    # Builds the collection of transitions for all event attributes defined on
+    # the given object.  This will only include events whose machine actions
+    # match the one specified.
+    # 
+    # These should only be fired as a result of the action being run.
+    def transitions(object, action, options = {})
+      transitions = map do |name, machine|
+        machine.events.attribute_transition_for(object, true) if machine.action == action
+      end
+
+      AttributeTransitionCollection.new(transitions.compact, {use_transactions: resolve_use_transactions}.merge(options))
+    end
+
+    protected
+
+    def resolve_use_transactions
+      use_transactions = nil
+      each_value do |machine|
+        # Determine use_transactions setting for this set of transitions.  If from multiple state_machines, the settings must match.
+        raise 'Encountered mismatched use_transactions configurations for multiple state_machines' if !use_transactions.nil? && use_transactions != machine.use_transactions
+        use_transactions = machine.use_transactions
+      end
+      use_transactions
+    end
+  end
+end
diff --git a/lib/state_machines/macro_methods.rb b/lib/state_machines/macro_methods.rb
new file mode 100644
index 0000000..d6b05fe
--- /dev/null
+++ b/lib/state_machines/macro_methods.rb
@@ -0,0 +1,520 @@
+# A state machine is a model of behavior composed of states, events, and
+# transitions.  This helper adds support for defining this type of
+# functionality on any Ruby class.
+module StateMachines
+  module MacroMethods
+    # Creates a new state machine with the given name.  The default name, if not
+    # specified, is <tt>:state</tt>.
+    # 
+    # Configuration options:
+    # * <tt>:attribute</tt> - The name of the attribute to store the state value
+    #   in.  By default, this is the same as the name of the machine.
+    # * <tt>:initial</tt> - The initial state of the attribute. This can be a
+    #   static state or a lambda block which will be evaluated at runtime
+    #   (e.g. lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling}).
+    #   Default is nil.
+    # * <tt>:initialize</tt> - Whether to automatically initialize the attribute
+    #   by hooking into #initialize on the owner class.  Default is true.
+    # * <tt>:action</tt> - The instance method to invoke when an object
+    #   transitions. Default is nil unless otherwise specified by the
+    #   configured integration.
+    # * <tt>:namespace</tt> - The name to use for namespacing all generated
+    #   state / event instance methods (e.g. "heater" would generate
+    #   :turn_on_heater and :turn_off_heater for the :turn_on/:turn_off events).
+    #   Default is nil.
+    # * <tt>:integration</tt> - The name of the integration to use for adding
+    #   library-specific behavior to the machine.  Built-in integrations
+    #   include :active_model, :active_record, :data_mapper, :mongo_mapper, and
+    #   :sequel.  By default, this is determined automatically.
+    # 
+    # Configuration options relevant to ORM integrations:
+    # * <tt>:plural</tt> - The pluralized version of the name.  By default, this
+    #   will attempt to call +pluralize+ on the name.  If this method is not
+    #   available, an "s" is appended.  This is used for generating scopes.
+    # * <tt>:messages</tt> - The error messages to use when invalidating
+    #   objects due to failed transitions.  Messages include:
+    #   * <tt>:invalid</tt>
+    #   * <tt>:invalid_event</tt>
+    #   * <tt>:invalid_transition</tt>
+    # * <tt>:use_transactions</tt> - Whether transactions should be used when
+    #   firing events.  Default is true unless otherwise specified by the
+    #   configured integration.
+    # 
+    # This also expects a block which will be used to actually configure the
+    # states, events and transitions for the state machine.  *Note* that this
+    # block will be executed within the context of the state machine.  As a
+    # result, you will not be able to access any class methods unless you refer
+    # to them directly (i.e. specifying the class name).
+    # 
+    # For examples on the types of state machine configurations and blocks, see
+    # the section below.
+    # 
+    # == Examples
+    # 
+    # With the default name/attribute and no configuration:
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       event :park do
+    #         ...
+    #       end
+    #     end
+    #   end
+    # 
+    # The above example will define a state machine named "state" that will
+    # store the value in the +state+ attribute.  Every vehicle will start
+    # without an initial state.
+    # 
+    # With a custom name / attribute:
+    # 
+    #   class Vehicle
+    #     state_machine :status, :attribute => :status_value do
+    #       ...
+    #     end
+    #   end
+    # 
+    # With a static initial state:
+    # 
+    #   class Vehicle
+    #     state_machine :status, :initial => :parked do
+    #       ...
+    #     end
+    #   end
+    # 
+    # With a dynamic initial state:
+    # 
+    #   class Vehicle
+    #     state_machine :status, :initial => lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling} do
+    #       ...
+    #     end
+    #   end
+    # 
+    # == Class Methods
+    # 
+    # The following class methods will be automatically generated by the
+    # state machine based on the *name* of the machine.  Any existing methods
+    # will not be overwritten.
+    # * <tt>human_state_name(state)</tt> - Gets the humanized value for the
+    #   given state.  This may be generated by internationalization libraries if
+    #   supported by the integration.
+    # * <tt>human_state_event_name(event)</tt> - Gets the humanized value for
+    #   the given event.  This may be generated by internationalization
+    #   libraries if supported by the integration.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine :state, :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #       
+    #       event :shift_up do
+    #         transition :idling => :first_gear
+    #       end
+    #     end
+    #   end
+    #   
+    #   Vehicle.human_state_name(:parked)         # => "parked"
+    #   Vehicle.human_state_name(:first_gear)     # => "first gear"
+    #   Vehicle.human_state_event_name(:park)     # => "park"
+    #   Vehicle.human_state_event_name(:shift_up) # => "shift up"
+    # 
+    # == Instance Methods
+    # 
+    # The following instance methods will be automatically generated by the
+    # state machine based on the *name* of the machine.  Any existing methods
+    # will not be overwritten.
+    # * <tt>state</tt> - Gets the current value for the attribute
+    # * <tt>state=(value)</tt> - Sets the current value for the attribute
+    # * <tt>state?(name)</tt> - Checks the given state name against the current
+    #   state.  If the name is not a known state, then an ArgumentError is raised.
+    # * <tt>state_name</tt> - Gets the name of the state for the current value
+    # * <tt>human_state_name</tt> - Gets the human-readable name of the state
+    #   for the current value
+    # * <tt>state_events(requirements = {})</tt> - Gets the list of events that
+    #   can be fired on the current object's state (uses the *unqualified* event
+    #   names)
+    # * <tt>state_transitions(requirements = {})</tt> - Gets the list of
+    #   transitions that can be made on the current object's state
+    # * <tt>state_paths(requirements = {})</tt> - Gets the list of sequences of
+    #   transitions that can be run from the current object's state
+    # * <tt>fire_state_event(name, *args)</tt> - Fires an arbitrary event with
+    #   the given argument list. This is essentially the same as calling the
+    #   actual event method itself.
+    # 
+    # The <tt>state_events</tt>, <tt>state_transitions</tt>, and <tt>state_paths</tt>
+    # helpers all take an optional set of requirements for determining what's
+    # available for the current object.  These requirements include:
+    # * <tt>:from</tt> - One or more states to transition from.  If none are
+    #   specified, then this will be the object's current state.
+    # * <tt>:to</tt> - One or more states to transition to.  If none are
+    #   specified, then this will match any to state.
+    # * <tt>:on</tt> - One or more events to transition on.  If none are
+    #   specified, then this will match any event.
+    # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
+    #   conditionals defined for each one.  Default is true.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine :state, :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #       
+    #       event :park do
+    #         transition :idling => :parked
+    #       end
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new
+    #   vehicle.state                             # => "parked"
+    #   vehicle.state_name                        # => :parked
+    #   vehicle.human_state_name                  # => "parked"
+    #   vehicle.state?(:parked)                   # => true
+    #   
+    #   # Changing state
+    #   vehicle.state = 'idling'
+    #   vehicle.state                             # => "idling"
+    #   vehicle.state_name                        # => :idling
+    #   vehicle.state?(:parked)                   # => false
+    #   
+    #   # Getting current event / transition availability
+    #   vehicle.state_events                      # => [:park]
+    #   vehicle.park                              # => true
+    #   vehicle.state_events                      # => [:ignite]
+    #   vehicle.state_events(:from => :idling)    # => [:park]
+    #   vehicle.state_events(:to => :parked)      # => []
+    #   
+    #   vehicle.state_transitions                 # => [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
+    #   vehicle.ignite                            # => true
+    #   vehicle.state_transitions                 # => [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
+    #   
+    #   vehicle.state_transitions(:on => :ignite) # => []
+    #   
+    #   # Getting current path availability
+    #   vehicle.state_paths                       # => [
+    #                                             #     [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>,
+    #                                             #      #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
+    #                                             #   ]
+    #   vehicle.state_paths(:guard => false)      # => 
+    #                                             #     [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>,
+    #                                             #      #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
+    #                                             #   ]
+    #   
+    #   # Fire arbitrary events
+    #   vehicle.fire_state_event(:park)           # => true
+    # 
+    # == Attribute initialization
+    # 
+    # For most classes, the initial values for state machine attributes are
+    # automatically assigned when a new object is created.  However, this
+    # behavior will *not* work if the class defines an +initialize+ method
+    # without properly calling +super+.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine :state, :initial => :parked do
+    #       ...
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new   # => #<Vehicle:0xb7c8dbf8 @state="parked">
+    #   vehicle.state           # => "parked"
+    # 
+    # In the above example, no +initialize+ method is defined.  As a result,
+    # the default behavior of initializing the state machine attributes is used.
+    # 
+    # In the following example, a custom +initialize+ method is defined:
+    # 
+    #   class Vehicle
+    #     state_machine :state, :initial => :parked do
+    #       ...
+    #     end
+    #     
+    #     def initialize
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new   # => #<Vehicle:0xb7c77678>
+    #   vehicle.state           # => nil
+    # 
+    # Since the +initialize+ method is defined, the state machine attributes
+    # never get initialized.  In order to ensure that all initialization hooks
+    # are called, the custom method *must* call +super+ without any arguments
+    # like so:
+    # 
+    #   class Vehicle
+    #     state_machine :state, :initial => :parked do
+    #       ...
+    #     end
+    #     
+    #     def initialize(attributes = {})
+    #       ...
+    #       super()
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new   # => #<Vehicle:0xb7c8dbf8 @state="parked">
+    #   vehicle.state           # => "parked"
+    # 
+    # Because of the way the inclusion of modules works in Ruby, calling
+    # <tt>super()</tt> will not only call the superclass's +initialize+, but
+    # also +initialize+ on all included modules.  This allows the original state
+    # machine hook to get called properly.
+    # 
+    # If you want to avoid calling the superclass's constructor, but still want
+    # to initialize the state machine attributes:
+    # 
+    #   class Vehicle
+    #     state_machine :state, :initial => :parked do
+    #       ...
+    #     end
+    #     
+    #     def initialize(attributes = {})
+    #       ...
+    #       initialize_state_machines
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new   # => #<Vehicle:0xb7c8dbf8 @state="parked">
+    #   vehicle.state           # => "parked"
+    # 
+    # You may also need to call the +initialize_state_machines+ helper manually
+    # in cases where you want to change how static / dynamic initial states get
+    # set.  For example, the following example forces the initialization of
+    # static states regardless of their current value:
+    # 
+    #   class Vehicle
+    #     state_machine :state, :initial => :parked do
+    #       state nil, :idling
+    #       ...
+    #     end
+    #     
+    #     def initialize(attributes = {})
+    #       @state = 'idling'
+    #       initialize_state_machines(:static => :force) do
+    #         ...
+    #       end
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new   # => #<Vehicle:0xb7c8dbf8 @state="parked">
+    #   vehicle.state           # => "parked"
+    # 
+    # The above example is also noteworthy because it demonstrates how to avoid
+    # initialization issues when +nil+ is a valid state.  Without passing in
+    # <tt>:static => :force</tt>, state_machine would never have initialized
+    # the state because +nil+ (the default attribute value) would have been
+    # interpreted as a valid current state.  As a result, state_machine would
+    # have simply skipped initialization.
+    # 
+    # == States
+    # 
+    # All of the valid states for the machine are automatically tracked based
+    # on the events, transitions, and callbacks defined for the machine.  If
+    # there are additional states that are never referenced, these should be
+    # explicitly added using the StateMachines::Machine#state or
+    # StateMachines::Machine#other_states helpers.
+    # 
+    # When a new state is defined, a predicate method for that state is
+    # generated on the class.  For example,
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition all => :idling
+    #       end
+    #     end
+    #   end
+    # 
+    # ...will generate the following instance methods (assuming they're not
+    # already defined in the class):
+    # * <tt>parked?</tt>
+    # * <tt>idling?</tt>
+    # 
+    # Each predicate method will return true if it matches the object's
+    # current state.  Otherwise, it will return false.
+    # 
+    # == Attribute access
+    # 
+    # The actual value for a state is stored in the attribute configured for the
+    # state machine.  In most cases, this is the same as the name of the state
+    # machine.  For example:
+    # 
+    #   class Vehicle
+    #     attr_accessor :state
+    #     
+    #     state_machine :state, :initial => :parked do
+    #       ...
+    #       state :parked, :value => 0
+    #       start :idling, :value => 1
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new # => #<Vehicle:0xb712da60 @state=0>
+    #   vehicle.state         # => 0
+    #   vehicle.parked?       # => true
+    #   vehicle.state = 1
+    #   vehicle.idling?       # => true
+    # 
+    # The most important thing to note from the example above is what it means
+    # to read from and write to the state machine's attribute.  In particular,
+    # state_machine treats the attribute (+state+ in this case) like a basic
+    # attr_accessor that's been defined on the class.  There are no special
+    # behaviors added, such as allowing the attribute to be written to based on
+    # the name of a state in the machine.  This is the case for a few reasons:
+    # * Setting the attribute directly is an edge case that is meant to only be
+    #   used when you want to skip state_machine altogether.  This means that
+    #   state_machine shouldn't have any effect on the attribute accessor
+    #   methods.  If you want to change the state, you should be using one of
+    #   the events defined in the state machine.
+    # * Many ORMs provide custom behavior for the attribute reader / writer - it
+    #   may even be defined by your own framework / method implementation just
+    #   the example above showed.  In order to avoid having to worry about the
+    #   different ways an attribute can get written, state_machine just makes
+    #   sure that the configured value for a state is always used when writing
+    #   to the attribute.
+    # 
+    # If you were interested in accessing the name of a state (instead of its
+    # actual value through the attribute), you could do the following:
+    # 
+    #   vehicle.state_name    # => :idling
+    # 
+    # == Events and Transitions
+    # 
+    # Events defined on the machine are the interface to transitioning states
+    # for an object.  Events can be fired either directly (through the method
+    # generated for the event) or indirectly (through attributes defined on
+    # the machine).
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     include DataMapper::Resource
+    #     property :id, Serial
+    #     
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #     end
+    #     
+    #     state_machine :alarm_state, :initial => :active do
+    #       event :disable do
+    #         transition all => :off
+    #       end
+    #     end
+    #   end
+    #   
+    #   # Fire +ignite+ event directly
+    #   vehicle = Vehicle.create    # => #<Vehicle id=1 state="parked" alarm_state="active">
+    #   vehicle.ignite              # => true
+    #   vehicle.state               # => "idling"
+    #   vehicle.alarm_state         # => "active"
+    #   
+    #   # Fire +disable+ event automatically
+    #   vehicle.alarm_state_event = 'disable'
+    #   vehicle.save                # => true
+    #   vehicle.alarm_state         # => "off"
+    # 
+    # In the above example, the +state+ attribute is transitioned using the
+    # +ignite+ action that's generated from the state machine.  On the other
+    # hand, the +alarm_state+ attribute is transitioned using the +alarm_state_event+
+    # attribute that automatically gets fired when the machine's action (+save+)
+    # is invoked.
+    # 
+    # For more information about how to configure an event and its associated
+    # transitions, see StateMachines::Machine#event.
+    # 
+    # == Defining callbacks
+    # 
+    # Within the +state_machine+ block, you can also define callbacks for
+    # transitions.  For more information about defining these callbacks,
+    # see StateMachines::Machine#before_transition, StateMachines::Machine#after_transition,
+    # and StateMachines::Machine#around_transition, and StateMachines::Machine#after_failure.
+    # 
+    # == Namespaces
+    # 
+    # When a namespace is configured for a state machine, the name provided
+    # will be used in generating the instance methods for interacting with
+    # states/events in the machine.  This is particularly useful when a class
+    # has multiple state machines and it would be difficult to differentiate
+    # between the various states / events.
+    # 
+    # For example,
+    # 
+    #   class Vehicle
+    #     state_machine :heater_state, :initial => :off, :namespace => 'heater' do
+    #       event :turn_on do
+    #         transition all => :on
+    #       end
+    #       
+    #       event :turn_off do
+    #         transition all => :off
+    #       end
+    #     end
+    #     
+    #     state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
+    #       event :turn_on do
+    #         transition all => :active
+    #       end
+    #       
+    #       event :turn_off do
+    #         transition all => :off
+    #       end
+    #     end
+    #   end
+    # 
+    # The above class defines two state machines: +heater_state+ and +alarm_state+.
+    # For the +heater_state+ machine, the following methods are generated since
+    # it's namespaced by "heater":
+    # * <tt>can_turn_on_heater?</tt>
+    # * <tt>turn_on_heater</tt>
+    # * ...
+    # * <tt>can_turn_off_heater?</tt>
+    # * <tt>turn_off_heater</tt>
+    # * ..
+    # * <tt>heater_off?</tt>
+    # * <tt>heater_on?</tt>
+    # 
+    # As shown, each method is unique to the state machine so that the states
+    # and events don't conflict.  The same goes for the +alarm_state+ machine:
+    # * <tt>can_turn_on_alarm?</tt>
+    # * <tt>turn_on_alarm</tt>
+    # * ...
+    # * <tt>can_turn_off_alarm?</tt>
+    # * <tt>turn_off_alarm</tt>
+    # * ..
+    # * <tt>alarm_active?</tt>
+    # * <tt>alarm_off?</tt>
+    # 
+    # == Scopes
+    # 
+    # For integrations that support it, a group of default scope filters will
+    # be automatically created for assisting in finding objects that have the
+    # attribute set to one of a given set of states.
+    # 
+    # For example,
+    # 
+    #   Vehicle.with_state(:parked)               # => All vehicles where the state is parked
+    #   Vehicle.with_states(:parked, :idling)     # => All vehicles where the state is either parked or idling
+    #   
+    #   Vehicle.without_state(:parked)            # => All vehicles where the state is *not* parked
+    #   Vehicle.without_states(:parked, :idling)  # => All vehicles where the state is *not* parked or idling
+    # 
+    # *Note* that if class methods already exist with those names (i.e.
+    # :with_state, :with_states, :without_state, or :without_states), then a
+    # scope will not be defined for that name.
+    # 
+    # See StateMachines::Machine for more information about using integrations
+    # and the individual integration docs for information about the actual
+    # scopes that are generated.
+    def state_machine(*args, &block)
+      StateMachines::Machine.find_or_create(self, *args, &block)
+    end
+  end
+end
diff --git a/lib/state_machines/matcher.rb b/lib/state_machines/matcher.rb
new file mode 100644
index 0000000..01e5f4d
--- /dev/null
+++ b/lib/state_machines/matcher.rb
@@ -0,0 +1,121 @@
+module StateMachines
+  # Provides a general strategy pattern for determining whether a match is found
+  # for a value.  The algorithm that actually determines the match depends on
+  # the matcher in use.
+  class Matcher
+    # The list of values against which queries are matched
+    attr_reader :values
+    
+    # Creates a new matcher for querying against the given set of values
+    def initialize(values = [])
+      @values = values.is_a?(Array) ? values : [values]
+    end
+    
+    # Generates a subset of values that exists in both the set of values being
+    # filtered and the values configured for the matcher
+    def filter(values)
+      self.values & values
+    end
+  end
+  
+  # Matches any given value.  Since there is no configuration for this type of
+  # matcher, it must be used as a singleton.
+  class AllMatcher < Matcher
+    include Singleton
+    
+    # Generates a blacklist matcher based on the given set of values
+    # 
+    # == Examples
+    # 
+    #   matcher = StateMachines::AllMatcher.instance - [:parked, :idling]
+    #   matcher.matches?(:parked)       # => false
+    #   matcher.matches?(:first_gear)   # => true
+    def -(blacklist)
+      BlacklistMatcher.new(blacklist)
+    end
+    
+    # Always returns true
+    def matches?(value, context = {})
+      true
+    end
+    
+    # Always returns the given set of values
+    def filter(values)
+      values
+    end
+    
+    # A human-readable description of this matcher.  Always "all".
+    def description
+      'all'
+    end
+  end
+  
+  # Matches a specific set of values
+  class WhitelistMatcher < Matcher
+    # Checks whether the given value exists within the whitelist configured
+    # for this matcher.
+    # 
+    # == Examples
+    # 
+    #   matcher = StateMachines::WhitelistMatcher.new([:parked, :idling])
+    #   matcher.matches?(:parked)       # => true
+    #   matcher.matches?(:first_gear)   # => false
+    def matches?(value, context = {})
+      values.include?(value)
+    end
+    
+    # A human-readable description of this matcher
+    def description
+      values.length == 1 ? values.first.inspect : values.inspect
+    end
+  end
+  
+  # Matches everything but a specific set of values
+  class BlacklistMatcher < Matcher
+    # Checks whether the given value exists outside the blacklist configured
+    # for this matcher.
+    # 
+    # == Examples
+    # 
+    #   matcher = StateMachines::BlacklistMatcher.new([:parked, :idling])
+    #   matcher.matches?(:parked)       # => false
+    #   matcher.matches?(:first_gear)   # => true
+    def matches?(value, context = {})
+      !values.include?(value)
+    end
+    
+    # Finds all values that are *not* within the blacklist configured for this
+    # matcher
+    def filter(values)
+      values - self.values
+    end
+    
+    # A human-readable description of this matcher
+    def description
+      "all - #{values.length == 1 ? values.first.inspect : values.inspect}"
+    end
+  end
+  
+  # Matches a loopback of two values within a context.  Since there is no
+  # configuration for this type of matcher, it must be used as a singleton.
+  class LoopbackMatcher < Matcher
+    include Singleton
+    
+    # Checks whether the given value matches what the value originally was.
+    # This value should be defined in the context.
+    # 
+    # == Examples
+    # 
+    #   matcher = StateMachines::LoopbackMatcher.instance
+    #   matcher.matches?(:parked, :from => :parked)   # => true
+    #   matcher.matches?(:parked, :from => :idling)   # => false
+    def matches?(value, context)
+      context[:from] == value
+    end
+    
+    # A human-readable description of this matcher.  Always "same".
+    def description
+      'same'
+    end
+  end
+end
diff --git a/lib/state_machines/matcher_helpers.rb b/lib/state_machines/matcher_helpers.rb
new file mode 100644
index 0000000..056879e
--- /dev/null
+++ b/lib/state_machines/matcher_helpers.rb
@@ -0,0 +1,54 @@
+module StateMachines
+  # Provides a set of helper methods for generating matchers
+  module MatcherHelpers
+    # Represents a state that matches all known states in a machine.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       before_transition any => :parked, :do => lambda {...}
+    #       before_transition all - :parked => all - :idling, :do => lambda {}
+    #       
+    #       event :park
+    #         transition all => :parked
+    #       end
+    #       
+    #       event :crash
+    #         transition all - :parked => :stalled
+    #       end
+    #     end
+    #   end
+    # 
+    # In the above example, +all+ will match the following states since they
+    # are known:
+    # * +parked+
+    # * +stalled+
+    # * +idling+
+    def all
+      AllMatcher.instance
+    end
+    alias_method :any, :all
+    
+    # Represents a state that matches the original +from+ state.  This is useful
+    # for defining transitions which are loopbacks.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       event :ignite
+    #         transition [:idling, :first_gear] => same
+    #       end
+    #     end
+    #   end
+    # 
+    # In the above example, +same+ will match whichever the from state is.  In
+    # the case of the +ignite+ event, it is essential the same as the following:
+    # 
+    #   transition :parked => :parked, :first_gear => :first_gear
+    def same
+      LoopbackMatcher.instance
+    end
+  end
+end
diff --git a/lib/state_machines/node_collection.rb b/lib/state_machines/node_collection.rb
new file mode 100644
index 0000000..9ed3c44
--- /dev/null
+++ b/lib/state_machines/node_collection.rb
@@ -0,0 +1,219 @@
+module StateMachines
+  # Represents a collection of nodes in a state machine, be it events or states.
+  # Nodes will not differentiate between the String and Symbol versions of the
+  # values being indexed.
+  class NodeCollection
+    include Enumerable
+
+    # The machine associated with the nodes
+    attr_reader :machine
+
+    # Creates a new collection of nodes for the given state machine.  By default,
+    # the collection is empty.
+    #
+    # Configuration options:
+    # * <tt>:index</tt> - One or more attributes to automatically generate
+    #   hashed indices for in order to perform quick lookups.  Default is to
+    #   index by the :name attribute
+    def initialize(machine, options = {})
+      options.assert_valid_keys(:index)
+      options = { index: :name }.merge(options)
+
+      @machine = machine
+      @nodes = []
+      @index_names = Array(options[:index])
+      @indices = @index_names.reduce({}) do |indices, name|
+        indices[name] = {}
+        indices[:"#{name}_to_s"] = {}
+        indices[:"#{name}_to_sym"] = {}
+        indices
+      end
+      @default_index = Array(options[:index]).first
+      @contexts = []
+    end
+
+    # Creates a copy of this collection such that modifications don't affect
+    # the original collection
+    def initialize_copy(orig) #:nodoc:
+      super
+
+      nodes = @nodes
+      contexts = @contexts
+      @nodes = []
+      @contexts = []
+      @indices = @indices.reduce({}) { |indices, (name, *)| indices[name] = {}; indices }
+
+      # Add nodes *prior* to copying over the contexts so that they don't get
+      # evaluated multiple times
+      concat(nodes.map { |n| n.dup })
+      @contexts = contexts.dup
+    end
+
+    # Changes the current machine associated with the collection.  In turn, this
+    # will change the state machine associated with each node in the collection.
+    def machine=(new_machine)
+      @machine = new_machine
+      each { |node| node.machine = new_machine }
+    end
+
+    # Gets the number of nodes in this collection
+    def length
+      @nodes.length
+    end
+
+    # Gets the set of unique keys for the given index
+    def keys(index_name = @default_index)
+      index(index_name).keys
+    end
+
+    # Tracks a context that should be evaluated for any nodes that get added
+    # which match the given set of nodes.  Matchers can be used so that the
+    # context can get added once and evaluated after multiple adds.
+    def context(nodes, &block)
+      nodes = nodes.first.is_a?(Matcher) ? nodes.first : WhitelistMatcher.new(nodes)
+      @contexts << context = { nodes: nodes, block: block }
+
+      # Evaluate the new context for existing nodes
+      each { |node| eval_context(context, node) }
+
+      context
+    end
+
+    # Adds a new node to the collection.  By doing so, this will also add it to
+    # the configured indices.  This will also evaluate any existings contexts
+    # that match the new node.
+    def <<(node)
+      @nodes << node
+      @index_names.each { |name| add_to_index(name, value(node, name), node) }
+      @contexts.each { |context| eval_context(context, node) }
+      self
+    end
+
+    # Appends a group of nodes to the collection
+    def concat(nodes)
+      nodes.each { |node| self << node }
+    end
+
+    # Updates the indexed keys for the given node.  If the node's attribute
+    # has changed since it was added to the collection, the old indexed keys
+    # will be replaced with the updated ones.
+    def update(node)
+      @index_names.each { |name| update_index(name, node) }
+    end
+
+    # Calls the block once for each element in self, passing that element as a
+    # parameter.
+    #
+    #   states = StateMachines::NodeCollection.new
+    #   states << StateMachines::State.new(machine, :parked)
+    #   states << StateMachines::State.new(machine, :idling)
+    #   states.each {|state| puts state.name, ' -- '}
+    #
+    # ...produces:
+    #
+    #   parked -- idling --
+    def each
+      @nodes.each { |node| yield node }
+      self
+    end
+
+    # Gets the node at the given index.
+    #
+    #   states = StateMachines::NodeCollection.new
+    #   states << StateMachines::State.new(machine, :parked)
+    #   states << StateMachines::State.new(machine, :idling)
+    #
+    #   states.at(0).name    # => :parked
+    #   states.at(1).name    # => :idling
+    def at(index)
+      @nodes[index]
+    end
+
+    # Gets the node indexed by the given key.  By default, this will look up the
+    # key in the first index configured for the collection.  A custom index can
+    # be specified like so:
+    #
+    #   collection['parked', :value]
+    #
+    # The above will look up the "parked" key in a hash indexed by each node's
+    # +value+ attribute.
+    #
+    # If the key cannot be found, then nil will be returned.
+    def [](key, index_name = @default_index)
+      index(index_name)[key] ||
+      index(:"#{index_name}_to_s")[key.to_s] ||
+      to_sym?(key) && index(:"#{index_name}_to_sym")[:"#{key}"] ||
+      nil
+    end
+
+    # Gets the node indexed by the given key.  By default, this will look up the
+    # key in the first index configured for the collection.  A custom index can
+    # be specified like so:
+    #
+    #   collection['parked', :value]
+    #
+    # The above will look up the "parked" key in a hash indexed by each node's
+    # +value+ attribute.
+    #
+    # If the key cannot be found, then an IndexError exception will be raised:
+    #
+    #   collection['invalid', :value]   # => IndexError: "invalid" is an invalid value
+    def fetch(key, index_name = @default_index)
+      self[key, index_name] || fail(IndexError, "#{key.inspect} is an invalid #{index_name}")
+    end
+
+    protected
+      # Gets the given index.  If the index does not exist, then an ArgumentError
+      # is raised.
+    def index(name)
+      fail ArgumentError, 'No indices configured' unless @indices.any?
+      @indices[name] || fail(ArgumentError, "Invalid index: #{name.inspect}")
+    end
+
+      # Gets the value for the given attribute on the node
+    def value(node, attribute)
+      node.send(attribute)
+    end
+
+      # Adds the given key / node combination to an index, including the string
+      # and symbol versions of the index
+    def add_to_index(name, key, node)
+      index(name)[key] = node
+      index(:"#{name}_to_s")[key.to_s] = node
+      index(:"#{name}_to_sym")[:"#{key}"] = node if to_sym?(key)
+    end
+
+      # Removes the given key from an index, including the string and symbol
+      # versions of the index
+    def remove_from_index(name, key)
+      index(name).delete(key)
+      index(:"#{name}_to_s").delete(key.to_s)
+      index(:"#{name}_to_sym").delete(:"#{key}") if to_sym?(key)
+    end
+
+      # Updates the node for the given index, including the string and symbol
+      # versions of the index
+    def update_index(name, node)
+      index = self.index(name)
+      old_key = index.key(node)
+      new_key = value(node, name)
+
+      # Only replace the key if it's changed
+      if old_key != new_key
+        remove_from_index(name, old_key)
+        add_to_index(name, new_key, node)
+      end
+    end
+
+      # Determines whether the given value can be converted to a symbol
+    def to_sym?(value)
+      "#{value}" != ''
+    end
+
+      # Evaluates the given context for a particular node.  This will only
+      # evaluate the context if the node matches.
+    def eval_context(context, node)
+      node.context(&context[:block]) if context[:nodes].matches?(node.name)
+    end
+  end
+end
diff --git a/lib/state_machines/path.rb b/lib/state_machines/path.rb
new file mode 100644
index 0000000..3d20a8a
--- /dev/null
+++ b/lib/state_machines/path.rb
@@ -0,0 +1,120 @@
+module StateMachines
+  # A path represents a sequence of transitions that can be run for a particular
+  # object.  Paths can walk to new transitions, revealing all of the possible
+  # branches that can be encountered in the object's state machine.
+  class Path < Array
+
+    
+    # The object whose state machine is being walked
+    attr_reader :object
+    
+    # The state machine this path is walking
+    attr_reader :machine
+    
+    # Creates a new transition path for the given object.  Initially this is an
+    # empty path.  In order to start walking the path, it must be populated with
+    # an initial transition.
+    # 
+    # Configuration options:
+    # * <tt>:target</tt> - The target state to end the path on
+    # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
+    #   conditionals defined for each one
+    def initialize(object, machine, options = {})
+      options.assert_valid_keys(:target, :guard)
+      
+      @object = object
+      @machine = machine
+      @target = options[:target]
+      @guard = options[:guard]
+    end
+    
+    def initialize_copy(orig) #:nodoc:
+      super
+      @transitions = nil
+    end
+    
+    # The initial state name for this path
+    def from_name
+      first && first.from_name
+    end
+    
+    # Lists all of the from states that can be reached through this path.
+    # 
+    # For example,
+    # 
+    #   path.to_states  # => [:parked, :idling, :first_gear, ...]
+    def from_states
+      map {|transition| transition.from_name}.uniq
+    end
+    
+    # The end state name for this path.  If a target state was specified for
+    # the path, then that will be returned if the path is complete.
+    def to_name
+      last && last.to_name
+    end
+    
+    # Lists all of the to states that can be reached through this path.
+    # 
+    # For example,
+    # 
+    #   path.to_states  # => [:parked, :idling, :first_gear, ...]
+    def to_states
+      map {|transition| transition.to_name}.uniq
+    end
+    
+    # Lists all of the events that can be fired through this path.
+    # 
+    # For example,
+    # 
+    #   path.events # => [:park, :ignite, :shift_up, ...]
+    def events
+      map {|transition| transition.event}.uniq
+    end
+    
+    # Walks down the next transitions at the end of this path.  This will only
+    # walk down paths that are considered valid.
+    def walk
+      transitions.each {|transition| yield dup.push(transition)}
+    end
+    
+    # Determines whether or not this path has completed.  A path is considered
+    # complete when one of the following conditions is met:
+    # * The last transition in the path ends on the target state
+    # * There are no more transitions remaining to walk and there is no target
+    #   state
+    def complete?
+      !empty? && (@target ? to_name == @target : transitions.empty?)
+    end
+    
+    private
+      # Calculates the number of times the given state has been walked to
+      def times_walked_to(state)
+        select {|transition| transition.to_name == state}.length
+      end
+      
+      # Determines whether the given transition has been recently walked down in
+      # this path.  If a target is configured for this path, then this will only
+      # look at transitions walked down since the target was last reached.
+      def recently_walked?(transition)
+        transitions = self
+        if @target && @target != to_name && target_transition = detect {|t| t.to_name == @target}
+          transitions = transitions[index(target_transition) + 1..-1]
+        end
+        transitions.include?(transition)
+      end
+      
+      # Determines whether it's possible to walk to the given transition from
+      # the current path.  A transition can be walked to if:
+      # * It has not been recently walked and
+      # * If a target is specified, it has not been walked to twice yet
+      def can_walk_to?(transition)
+        !recently_walked?(transition) && (!@target || times_walked_to(@target) < 2)
+      end
+      
+      # Get the next set of transitions that can be walked to starting from the
+      # end of this path
+      def transitions
+        @transitions ||= empty? ? [] : machine.events.transitions_for(object, :from => to_name, :guard => @guard).select {|transition| can_walk_to?(transition)}
+      end
+  end
+end
diff --git a/lib/state_machines/path_collection.rb b/lib/state_machines/path_collection.rb
new file mode 100644
index 0000000..13095b7
--- /dev/null
+++ b/lib/state_machines/path_collection.rb
@@ -0,0 +1,88 @@
+module StateMachines
+  # Represents a collection of paths that are generated based on a set of
+  # requirements regarding what states to start and end on
+  class PathCollection < Array
+
+    
+    # The object whose state machine is being walked
+    attr_reader :object
+    
+    # The state machine these path are walking
+    attr_reader :machine
+    
+    # The initial state to start each path from
+    attr_reader :from_name
+    
+    # The target state for each path
+    attr_reader :to_name
+    
+    # Creates a new collection of paths with the given requirements.
+    # 
+    # Configuration options:
+    # * <tt>:from</tt> - The initial state to start from
+    # * <tt>:to</tt> - The target end state
+    # * <tt>:deep</tt> - Whether to enable deep searches for the target state.
+    # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
+    #   conditionals defined for each one
+    def initialize(object, machine, options = {})
+      options = {:deep => false, :from => machine.states.match!(object).name}.merge(options)
+      options.assert_valid_keys( :from, :to, :deep, :guard)
+      
+      @object = object
+      @machine = machine
+      @from_name = machine.states.fetch(options[:from]).name
+      @to_name = options[:to] && machine.states.fetch(options[:to]).name
+      @guard = options[:guard]
+      @deep = options[:deep]
+      
+      initial_paths.each {|path| walk(path)}
+    end
+    
+    # Lists all of the states that can be transitioned from through the paths in
+    # this collection.
+    # 
+    # For example,
+    # 
+    #   paths.from_states # => [:parked, :idling, :first_gear, ...]
+    def from_states
+      map {|path| path.from_states}.flatten.uniq
+    end
+    
+    # Lists all of the states that can be transitioned to through the paths in
+    # this collection.
+    # 
+    # For example,
+    # 
+    #   paths.to_states # => [:idling, :first_gear, :second_gear, ...]
+    def to_states
+      map {|path| path.to_states}.flatten.uniq
+    end
+    
+    # Lists all of the events that can be fired through the paths in this
+    # collection.
+    # 
+    # For example,
+    # 
+    #   paths.events  # => [:park, :ignite, :shift_up, ...]
+    def events
+      map {|path| path.events}.flatten.uniq
+    end
+    
+    private
+      # Gets the initial set of paths to walk
+      def initial_paths
+        machine.events.transitions_for(object, :from => from_name, :guard => @guard).map do |transition|
+          path = Path.new(object, machine, :target => to_name, :guard => @guard)
+          path << transition
+          path
+        end
+      end
+      
+      # Walks down the given path.  Each new path that matches the configured
+      # requirements will be added to this collection.
+      def walk(path)
+        self << path if path.complete?
+        path.walk {|next_path| walk(next_path)} unless to_name && path.complete? && !@deep
+      end
+  end
+end
diff --git a/lib/state_machines/state.rb b/lib/state_machines/state.rb
new file mode 100644
index 0000000..5effce0
--- /dev/null
+++ b/lib/state_machines/state.rb
@@ -0,0 +1,272 @@
+module StateMachines
+  # A state defines a value that an attribute can be in after being transitioned
+  # 0 or more times.  States can represent a value of any type in Ruby, though
+  # the most common (and default) type is String.
+  # 
+  # In addition to defining the machine's value, a state can also define a
+  # behavioral context for an object when that object is in the state.  See
+  # StateMachines::Machine#state for more information about how state-driven
+  # behavior can be utilized.
+  class State
+
+    # The state machine for which this state is defined
+    attr_accessor :machine
+
+    # The unique identifier for the state used in event and callback definitions
+    attr_reader :name
+
+    # The fully-qualified identifier for the state, scoped by the machine's
+    # namespace
+    attr_reader :qualified_name
+
+    # The human-readable name for the state
+    attr_writer :human_name
+
+    # The value that is written to a machine's attribute when an object
+    # transitions into this state
+    attr_writer :value
+
+    # Whether this state's value should be cached after being evaluated
+    attr_accessor :cache
+
+    # Whether or not this state is the initial state to use for new objects
+    attr_accessor :initial
+    alias_method :initial?, :initial
+
+    # A custom lambda block for determining whether a given value matches this
+    # state
+    attr_accessor :matcher
+
+    # Creates a new state within the context of the given machine.
+    # 
+    # Configuration options:
+    # * <tt>:initial</tt> - Whether this state is the beginning state for the
+    #   machine. Default is false.
+    # * <tt>:value</tt> - The value to store when an object transitions to this
+    #   state.  Default is the name (stringified).
+    # * <tt>:cache</tt> - If a dynamic value (via a lambda block) is being used,
+    #   then setting this to true will cache the evaluated result
+    # * <tt>:if</tt> - Determines whether a value matches this state
+    #   (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
+    #   By default, the configured value is matched.
+    # * <tt>:human_name</tt> - The human-readable version of this state's name
+    def initialize(machine, name, options = {}) #:nodoc:
+      options.assert_valid_keys(:initial, :value, :cache, :if, :human_name)
+
+      @machine = machine
+      @name = name
+      @qualified_name = name && machine.namespace ? :"#{machine.namespace}_#{name}" : name
+      @human_name = options[:human_name] || (@name ? @name.to_s.tr('_', ' ') : 'nil')
+      @value = options.include?(:value) ? options[:value] : name && name.to_s
+      @cache = options[:cache]
+      @matcher = options[:if]
+      @initial = options[:initial] == true
+      @context = StateContext.new(self)
+
+      if name
+        conflicting_machines = machine.owner_class.state_machines.select { |other_name, other_machine| other_machine != machine && other_machine.states[qualified_name, :qualified_name] }
+
+        # Output a warning if another machine has a conflicting qualified name
+        # for a different attribute
+        if conflict = conflicting_machines.detect { |other_name, other_machine| other_machine.attribute != machine.attribute }
+          name, other_machine = conflict
+          warn "State #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
+        elsif conflicting_machines.empty?
+          # Only bother adding predicates when another machine for the same
+          # attribute hasn't already done so
+          add_predicate
+        end
+      end
+    end
+
+    # Creates a copy of this state, excluding the context to prevent conflicts
+    # across different machines.
+    def initialize_copy(orig) #:nodoc:
+      super
+      @context = StateContext.new(self)
+    end
+
+    # Determines whether there are any states that can be transitioned to from
+    # this state.  If there are none, then this state is considered *final*.
+    # Any objects in a final state will remain so forever given the current
+    # machine's definition.
+    def final?
+      !machine.events.any? do |event|
+        event.branches.any? do |branch|
+          branch.state_requirements.any? do |requirement|
+            requirement[:from].matches?(name) && !requirement[:to].matches?(name, :from => name)
+          end
+        end
+      end
+    end
+
+    # Transforms the state name into a more human-readable format, such as
+    # "first gear" instead of "first_gear"
+    def human_name(klass = @machine.owner_class)
+      @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name
+    end
+
+    # Generates a human-readable description of this state's name / value:
+    # 
+    # For example,
+    # 
+    #   State.new(machine, :parked).description                               # => "parked"
+    #   State.new(machine, :parked, :value => :parked).description            # => "parked"
+    #   State.new(machine, :parked, :value => nil).description                # => "parked (nil)"
+    #   State.new(machine, :parked, :value => 1).description                  # => "parked (1)"
+    #   State.new(machine, :parked, :value => lambda {Time.now}).description  # => "parked (*)
+    # 
+    # Configuration options:
+    # * <tt>:human_name</tt> - Whether to use this state's human name in the
+    #   description or just the internal name
+    def description(options = {})
+      label = options[:human_name] ? human_name : name
+      description = label ? label.to_s : label.inspect
+      description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s
+      description
+    end
+
+    # The value that represents this state.  This will optionally evaluate the
+    # original block if it's a lambda block.  Otherwise, the static value is
+    # returned.
+    # 
+    # For example,
+    # 
+    #   State.new(machine, :parked, :value => 1).value                        # => 1
+    #   State.new(machine, :parked, :value => lambda {Time.now}).value        # => Tue Jan 01 00:00:00 UTC 2008
+    #   State.new(machine, :parked, :value => lambda {Time.now}).value(false) # => <Proc:0xb6ea7ca0 at ...>
+    def value(eval = true)
+      if @value.is_a?(Proc) && eval
+        if cache_value?
+          @value = @value.call
+          machine.states.update(self)
+          @value
+        else
+          @value.call
+        end
+      else
+        @value
+      end
+    end
+
+    # Determines whether this state matches the given value.  If no matcher is
+    # configured, then this will check whether the values are equivalent.
+    # Otherwise, the matcher will determine the result.
+    # 
+    # For example,
+    # 
+    #   # Without a matcher
+    #   state = State.new(machine, :parked, :value => 1)
+    #   state.matches?(1)           # => true
+    #   state.matches?(2)           # => false
+    #   
+    #   # With a matcher
+    #   state = State.new(machine, :parked, :value => lambda {Time.now}, :if => lambda {|value| !value.nil?})
+    #   state.matches?(nil)         # => false
+    #   state.matches?(Time.now)    # => true
+    def matches?(other_value)
+      matcher ? matcher.call(other_value) : other_value == value
+    end
+
+    # Defines a context for the state which will be enabled on instances of
+    # the owner class when the machine is in this state.
+    # 
+    # This can be called multiple times.  Each time a new context is created,
+    # a new module will be included in the owner class.
+    def context(&block)
+      # Include the context
+      context = @context
+      machine.owner_class.class_eval { include context }
+
+      # Evaluate the method definitions and track which ones were added
+      old_methods = context_methods
+      context.class_eval(&block)
+      new_methods = context_methods.to_a.select { |(name, method)| old_methods[name] != method }
+
+      # Alias new methods so that the only execute when the object is in this state
+      new_methods.each do |(method_name, method)|
+        context_name = context_name_for(method_name)
+        context.class_eval <<-end_eval, __FILE__, __LINE__ + 1
+          alias_method :"#{context_name}", :#{method_name}
+          def #{method_name}(*args, &block)
+            state = self.class.state_machine(#{machine.name.inspect}).states.fetch(#{name.inspect})
+            options = {:method_missing => lambda {super(*args, &block)}, :method_name => #{method_name.inspect}}
+            state.call(self, :"#{context_name}", *(args + [options]), &block)
+          end
+        end_eval
+      end
+
+      true
+    end
+
+    # The list of methods that have been defined in this state's context
+    def context_methods
+      @context.instance_methods.inject({}) do |methods, name|
+        methods.merge(name.to_sym => @context.instance_method(name))
+      end
+    end
+
+    # Calls a method defined in this state's context on the given object.  All
+    # arguments and any block will be passed into the method defined.
+    # 
+    # If the method has never been defined for this state, then a NoMethodError
+    # will be raised.
+    def call(object, method, *args, &block)
+      options = args.last.is_a?(Hash) ? args.pop : {}
+      options = {:method_name => method}.merge(options)
+      state = machine.states.match!(object)
+
+      if state == self && object.respond_to?(method)
+        object.send(method, *args, &block)
+      elsif method_missing = options[:method_missing]
+        # Dispatch to the superclass since the object either isn't in this state
+        # or this state doesn't handle the method
+        begin
+          method_missing.call
+        rescue NoMethodError => ex
+          if ex.name.to_s == options[:method_name].to_s && ex.args == args
+            # No valid context for this method
+            raise InvalidContext.new(object, "State #{state.name.inspect} for #{machine.name.inspect} is not a valid context for calling ##{options[:method_name]}")
+          else
+            raise
+          end
+        end
+      end
+    end
+
+    def draw(graph, options = {})
+      fail NotImplementedError
+    end
+
+    # Generates a nicely formatted description of this state's contents.
+    # 
+    # For example,
+    # 
+    #   state = StateMachines::State.new(machine, :parked, :value => 1, :initial => true)
+    #   state   # => #<StateMachines::State name=:parked value=1 initial=true context=[]>
+    def inspect
+      attributes = [[:name, name], [:value, @value], [:initial, initial?]]
+      "#<#{self.class} #{attributes.map { |attr, value| "#{attr}=#{value.inspect}" } * ' '}>"
+    end
+
+    private
+    # Should the value be cached after it's evaluated for the first time?
+    def cache_value?
+      @cache
+    end
+
+    # Adds a predicate method to the owner class so long as a name has
+    # actually been configured for the state
+    def add_predicate
+      # Checks whether the current value matches this state
+      machine.define_helper(:instance, "#{qualified_name}?") do |machine, object|
+        machine.states.matches?(object, name)
+      end
+    end
+
+    # Generates the name of the method containing the actual implementation
+    def context_name_for(method)
+      :"__#{machine.name}_#{name}_#{method}_#{@context.object_id}__"
+    end
+  end
+end
diff --git a/lib/state_machines/state_collection.rb b/lib/state_machines/state_collection.rb
new file mode 100644
index 0000000..db8b9dc
--- /dev/null
+++ b/lib/state_machines/state_collection.rb
@@ -0,0 +1,110 @@
+module StateMachines
+  # Represents a collection of states in a state machine
+  class StateCollection < NodeCollection
+    def initialize(machine) #:nodoc:
+      super(machine, :index => [:name, :qualified_name, :value])
+    end
+
+    # Determines whether the given object is in a specific state.  If the
+    # object's current value doesn't match the state, then this will return
+    # false, otherwise true.  If the given state is unknown, then an IndexError
+    # will be raised.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       other_states :idling
+    #     end
+    #   end
+    #   
+    #   states = Vehicle.state_machine.states
+    #   vehicle = Vehicle.new               # => #<Vehicle:0xb7c464b0 @state="parked">
+    #   
+    #   states.matches?(vehicle, :parked)   # => true
+    #   states.matches?(vehicle, :idling)   # => false
+    #   states.matches?(vehicle, :invalid)  # => IndexError: :invalid is an invalid key for :name index
+    def matches?(object, name)
+      fetch(name).matches?(machine.read(object, :state))
+    end
+
+    # Determines the current state of the given object as configured by this
+    # state machine.  This will attempt to find a known state that matches
+    # the value of the attribute on the object.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       other_states :idling
+    #     end
+    #   end
+    #   
+    #   states = Vehicle.state_machine.states
+    #   
+    #   vehicle = Vehicle.new         # => #<Vehicle:0xb7c464b0 @state="parked">
+    #   states.match(vehicle)         # => #<StateMachines::State name=:parked value="parked" initial=true>
+    #   
+    #   vehicle.state = 'idling'
+    #   states.match(vehicle)         # => #<StateMachines::State name=:idling value="idling" initial=true>
+    #   
+    #   vehicle.state = 'invalid'
+    #   states.match(vehicle)         # => nil
+    def match(object)
+      value = machine.read(object, :state)
+      self[value, :value] || detect { |state| state.matches?(value) }
+    end
+
+    # Determines the current state of the given object as configured by this
+    # state machine.  If no state is found, then an ArgumentError will be
+    # raised.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       other_states :idling
+    #     end
+    #   end
+    #   
+    #   states = Vehicle.state_machine.states
+    #   
+    #   vehicle = Vehicle.new         # => #<Vehicle:0xb7c464b0 @state="parked">
+    #   states.match!(vehicle)        # => #<StateMachines::State name=:parked value="parked" initial=true>
+    #   
+    #   vehicle.state = 'invalid'
+    #   states.match!(vehicle)        # => ArgumentError: "invalid" is not a known state value
+    def match!(object)
+      match(object) || raise(ArgumentError, "#{machine.read(object, :state).inspect} is not a known #{machine.name} value")
+    end
+
+    # Gets the order in which states should be displayed based on where they
+    # were first referenced.  This will order states in the following priority:
+    # 
+    # 1. Initial state
+    # 2. Event transitions (:from, :except_from, :to, :except_to options)
+    # 3. States with behaviors
+    # 4. States referenced via +state+ or +other_states+
+    # 5. States referenced in callbacks
+    # 
+    # This order will determine how the GraphViz visualizations are rendered.
+    def by_priority
+      order = select { |state| state.initial }.map { |state| state.name }
+
+      machine.events.each { |event| order += event.known_states }
+      order += select { |state| state.context_methods.any? }.map { |state| state.name }
+      order += keys(:name) - machine.callbacks.values.flatten.map { |callback| callback.known_states }.flatten
+      order += keys(:name)
+
+      order.uniq!
+      order.map! { |name| self[name] }
+      order
+    end
+
+    private
+    # Gets the value for the given attribute on the node
+    def value(node, attribute)
+      attribute == :value ? node.value(false) : super
+    end
+  end
+end
diff --git a/lib/state_machines/state_context.rb b/lib/state_machines/state_context.rb
new file mode 100644
index 0000000..11ba031
--- /dev/null
+++ b/lib/state_machines/state_context.rb
@@ -0,0 +1,133 @@
+module StateMachines
+
+  
+  # Represents a module which will get evaluated within the context of a state.
+  # 
+  # Class-level methods are proxied to the owner class, injecting a custom
+  # <tt>:if</tt> condition along with method.  This assumes that the method has
+  # support for a set of configuration options, including <tt>:if</tt>.  This
+  # condition will check that the object's state matches this context's state.
+  # 
+  # Instance-level methods are used to define state-driven behavior on the
+  # state's owner class.
+  # 
+  # == Examples
+  # 
+  #   class Vehicle
+  #     class << self
+  #       attr_accessor :validations
+  #       
+  #       def validate(options, &block)
+  #         validations << options
+  #       end
+  #     end
+  #     
+  #     self.validations = []
+  #     attr_accessor :state, :simulate
+  #     
+  #     def moving?
+  #       self.class.validations.all? {|validation| validation[:if].call(self)}
+  #     end
+  #   end
+  # 
+  # In the above class, a simple set of validation behaviors have been defined.
+  # Each validation consists of a configuration like so:
+  # 
+  #   Vehicle.validate :unless => :simulate
+  #   Vehicle.validate :if => lambda {|vehicle| ...}
+  # 
+  # In order to scope validations to a particular state context, the class-level
+  # +validate+ method can be invoked like so:
+  # 
+  #   machine = StateMachines::Machine.new(Vehicle)
+  #   context = StateMachines::StateContext.new(machine.state(:first_gear))
+  #   context.validate(:unless => :simulate)
+  #   
+  #   vehicle = Vehicle.new     # => #<Vehicle:0xb7ce491c @simulate=nil, @state=nil>
+  #   vehicle.moving?           # => false
+  #   
+  #   vehicle.state = 'first_gear'
+  #   vehicle.moving?           # => true
+  #   
+  #   vehicle.simulate = true
+  #   vehicle.moving?           # => false
+  class StateContext < Module
+
+    include EvalHelpers
+    
+    # The state machine for which this context's state is defined
+    attr_reader :machine
+    
+    # The state that must be present in an object for this context to be active
+    attr_reader :state
+    
+    # Creates a new context for the given state
+    def initialize(state)
+      @state = state
+      @machine = state.machine
+      
+      state_name = state.name
+      machine_name = machine.name
+      @condition = lambda {|object| object.class.state_machine(machine_name).states.matches?(object, state_name)}
+    end
+    
+    # Creates a new transition that determines what to change the current state
+    # to when an event fires from this state.
+    # 
+    # Since this transition is being defined within a state context, you do
+    # *not* need to specify the <tt>:from</tt> option for the transition.  For
+    # example:
+    # 
+    #   state_machine do
+    #     state :parked do
+    #       transition :to => :idling, :on => [:ignite, :shift_up]                          # Transitions to :idling
+    #       transition :from => [:idling, :parked], :on => :park, :unless => :seatbelt_on?  # Transitions to :parked if seatbelt is off
+    #     end
+    #   end
+    # 
+    # See StateMachines::Machine#transition for a description of the possible
+    # configurations for defining transitions.
+    def transition(options)
+      options.assert_valid_keys(:from, :to, :on, :if, :unless)
+      raise ArgumentError, 'Must specify :on event' unless options[:on]
+      raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from]
+      
+      machine.transition(options.merge(options[:to] ? {:from => state.name} : {:to => state.name}))
+    end
+    
+    # Hooks in condition-merging to methods that don't exist in this module
+    def method_missing(*args, &block)
+      # Get the configuration
+      if args.last.is_a?(Hash)
+        options = args.last
+      else
+        args << options = {}
+      end
+      
+      # Get any existing condition that may need to be merged
+      if_condition = options.delete(:if)
+      unless_condition = options.delete(:unless)
+      
+      # Provide scope access to configuration in case the block is evaluated
+      # within the object instance
+      proxy = self
+      proxy_condition = @condition
+      
+      # Replace the configuration condition with the one configured for this
+      # proxy, merging together any existing conditions
+      options[:if] = lambda do |*condition_args|
+        # Block may be executed within the context of the actual object, so
+        # it'll either be the first argument or the executing context
+        object = condition_args.first || self
+        
+        proxy.evaluate_method(object, proxy_condition) &&
+        Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} &&
+        !Array(unless_condition).any? {|condition| proxy.evaluate_method(object, condition)}
+      end
+      
+      # Evaluate the method on the owner class with the condition proxied
+      # through
+      machine.owner_class.send(*args, &block)
+    end
+  end
+end
diff --git a/lib/state_machines/transition.rb b/lib/state_machines/transition.rb
new file mode 100644
index 0000000..940195b
--- /dev/null
+++ b/lib/state_machines/transition.rb
@@ -0,0 +1,414 @@
+module StateMachines
+  # A transition represents a state change for a specific attribute.
+  # 
+  # Transitions consist of:
+  # * An event
+  # * A starting state
+  # * An ending state
+  class Transition
+    # The object being transitioned
+    attr_reader :object
+    
+    # The state machine for which this transition is defined
+    attr_reader :machine
+    
+    # The original state value *before* the transition
+    attr_reader :from
+    
+    # The new state value *after* the transition
+    attr_reader :to
+    
+    # The arguments passed in to the event that triggered the transition
+    # (does not include the +run_action+ boolean argument if specified)
+    attr_accessor :args
+    
+    # The result of invoking the action associated with the machine
+    attr_reader :result
+    
+    # Whether the transition is only existing temporarily for the object
+    attr_writer :transient
+    
+    # Determines whether the current ruby implementation supports pausing and
+    # resuming transitions
+    def self.pause_supported?
+      %w(ruby maglev).include?(RUBY_ENGINE)
+    end
+    
+    # Creates a new, specific transition
+    def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc:
+      @object = object
+      @machine = machine
+      @args = []
+      @transient = false
+      @resume_block = nil
+      
+      @event = machine.events.fetch(event)
+      @from_state = machine.states.fetch(from_name)
+      @from = read_state ? machine.read(object, :state) : @from_state.value
+      @to_state = machine.states.fetch(to_name)
+      @to = @to_state.value
+      
+      reset
+    end
+    
+    # The attribute which this transition's machine is defined for
+    def attribute
+      machine.attribute
+    end
+    
+    # The action that will be run when this transition is performed
+    def action
+      machine.action
+    end
+    
+    # The event that triggered the transition
+    def event
+      @event.name
+    end
+    
+    # The fully-qualified name of the event that triggered the transition
+    def qualified_event
+      @event.qualified_name
+    end
+    
+    # The human-readable name of the event that triggered the transition
+    def human_event
+      @event.human_name(@object.class)
+    end
+    
+    # The state name *before* the transition
+    def from_name
+      @from_state.name
+    end
+    
+    # The fully-qualified state name *before* the transition
+    def qualified_from_name
+      @from_state.qualified_name
+    end
+    
+    # The human-readable state name *before* the transition
+    def human_from_name
+      @from_state.human_name(@object.class)
+    end
+    
+    # The new state name *after* the transition
+    def to_name
+      @to_state.name
+    end
+    
+    # The new fully-qualified state name *after* the transition
+    def qualified_to_name
+      @to_state.qualified_name
+    end
+    
+    # The new human-readable state name *after* the transition
+    def human_to_name
+      @to_state.human_name(@object.class)
+    end
+    
+    # Does this transition represent a loopback (i.e. the from and to state
+    # are the same)
+    # 
+    # == Example
+    # 
+    #   machine = StateMachine.new(Vehicle)
+    #   StateMachines::Transition.new(Vehicle.new, machine, :park, :parked, :parked).loopback?   # => true
+    #   StateMachines::Transition.new(Vehicle.new, machine, :park, :idling, :parked).loopback?   # => false
+    def loopback?
+      from_name == to_name
+    end
+    
+    # Is this transition existing for a short period only?  If this is set, it
+    # indicates that the transition (or the event backing it) should not be
+    # written to the object if it fails.
+    def transient?
+      @transient
+    end
+    
+    # A hash of all the core attributes defined for this transition with their
+    # names as keys and values of the attributes as values.
+    # 
+    # == Example
+    # 
+    #   machine = StateMachine.new(Vehicle)
+    #   transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
+    #   transition.attributes   # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
+    def attributes
+      @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to}
+    end
+    
+    # Runs the actual transition and any before/after callbacks associated
+    # with the transition.  The action associated with the transition/machine
+    # can be skipped by passing in +false+.
+    # 
+    # == Examples
+    # 
+    #   class Vehicle
+    #     state_machine :action => :save do
+    #       ...
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new
+    #   transition = StateMachines::Transition.new(vehicle, machine, :ignite, :parked, :idling)
+    #   transition.perform                  # => Runs the +save+ action after setting the state attribute
+    #   transition.perform(false)           # => Only sets the state attribute
+    #   transition.perform(Time.now)        # => Passes in additional arguments and runs the +save+ action
+    #   transition.perform(Time.now, false) # => Passes in additional arguments and only sets the state attribute
+    def perform(*args)
+      run_action = [true, false].include?(args.last) ? args.pop : true
+      self.args = args
+      
+      # Run the transition
+      !!TransitionCollection.new([self], {use_transactions: machine.use_transactions, actions: run_action}).perform
+    end
+    
+    # Runs a block within a transaction for the object being transitioned.
+    # By default, transactions are a no-op unless otherwise defined by the
+    # machine's integration.
+    def within_transaction
+      machine.within_transaction(object) do
+        yield
+      end
+    end
+    
+    # Runs the before / after callbacks for this transition.  If a block is
+    # provided, then it will be executed between the before and after callbacks.
+    # 
+    # Configuration options:
+    # * +before+ - Whether to run before callbacks.
+    # * +after+ - Whether to run after callbacks.  If false, then any around
+    #   callbacks will be paused until called again with +after+ enabled.
+    #   Default is true.
+    # 
+    # This will return true if all before callbacks gets executed.  After
+    # callbacks will not have an effect on the result.
+    def run_callbacks(options = {}, &block)
+      options = {:before => true, :after => true}.merge(options)
+      @success = false
+      
+      halted = pausable { before(options[:after], &block) } if options[:before]
+      
+      # After callbacks are only run if:
+      # * An around callback didn't halt after yielding
+      # * They're enabled or the run didn't succeed
+      after if !(@before_run && halted) && (options[:after] || !@success)
+      
+      @before_run
+    end
+    
+    # Transitions the current value of the state to that specified by the
+    # transition.  Once the state is persisted, it cannot be persisted again
+    # until this transition is reset.
+    # 
+    # == Example
+    # 
+    #   class Vehicle
+    #     state_machine do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new
+    #   transition = StateMachines::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
+    #   transition.persist
+    #   
+    #   vehicle.state   # => 'idling'
+    def persist
+      unless @persisted
+        machine.write(object, :state, to)
+        @persisted = true
+      end
+    end
+    
+    # Rolls back changes made to the object's state via this transition.  This
+    # will revert the state back to the +from+ value.
+    # 
+    # == Example
+    # 
+    #   class Vehicle
+    #     state_machine :initial => :parked do
+    #       event :ignite do
+    #         transition :parked => :idling
+    #       end
+    #     end
+    #   end
+    #   
+    #   vehicle = Vehicle.new     # => #<Vehicle:0xb7b7f568 @state="parked">
+    #   transition = StateMachines::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
+    #   
+    #   # Persist the new state
+    #   vehicle.state             # => "parked"
+    #   transition.persist
+    #   vehicle.state             # => "idling"
+    #   
+    #   # Roll back to the original state
+    #   transition.rollback
+    #   vehicle.state             # => "parked"
+    def rollback
+      reset
+      machine.write(object, :state, from)
+    end
+    
+    # Resets any tracking of which callbacks have already been run and whether
+    # the state has already been persisted
+    def reset
+      @before_run = @persisted = @after_run = false
+      @paused_block = nil
+    end
+    
+    # Determines equality of transitions by testing whether the object, states,
+    # and event involved in the transition are equal
+    def ==(other)
+      other.instance_of?(self.class) &&
+      other.object == object &&
+      other.machine == machine &&
+      other.from_name == from_name &&
+      other.to_name == to_name &&
+      other.event == event
+    end
+    
+    # Generates a nicely formatted description of this transitions's contents.
+    # 
+    # For example,
+    # 
+    #   transition = StateMachines::Transition.new(object, machine, :ignite, :parked, :idling)
+    #   transition   # => #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
+    def inspect
+      "#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>"
+    end
+    
+    private
+      # Runs a block that may get paused.  If the block doesn't pause, then
+      # execution will continue as normal.  If the block gets paused, then it
+      # will take care of switching the execution context when it's resumed.
+      # 
+      # This will return true if the given block halts for a reason other than
+      # getting paused.
+      def pausable
+        begin
+          halted = !catch(:halt) { yield; true }
+        rescue Exception => error
+          raise unless @resume_block
+        end
+        
+        if @resume_block
+          @resume_block.call(halted, error)
+        else
+          halted
+        end
+      end
+      
+      # Pauses the current callback execution.  This should only occur within
+      # around callbacks when the remainder of the callback will be executed at
+      # a later point in time.
+      def pause
+        raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' unless self.class.pause_supported?
+        
+        unless @resume_block
+          require 'continuation' unless defined?(callcc)
+          callcc do |block|
+            @paused_block = block
+            throw :halt, true
+          end
+        end
+      end
+      
+      # Resumes the execution of a previously paused callback execution.  Once
+      # the paused callbacks complete, the current execution will continue.
+      def resume
+        if @paused_block
+          halted, error = callcc do |block|
+            @resume_block = block
+            @paused_block.call
+          end
+          
+          @resume_block = @paused_block = nil
+          
+          raise error if error
+          !halted
+        else
+          true
+        end
+      end
+      
+      # Runs the machine's +before+ callbacks for this transition.  Only
+      # callbacks that are configured to match the event, from state, and to
+      # state will be invoked.
+      # 
+      # Once the callbacks are run, they cannot be run again until this transition
+      # is reset.
+      def before(complete = true, index = 0, &block)
+        unless @before_run
+          while callback = machine.callbacks[:before][index]
+            index += 1
+            
+            if callback.type == :around
+              # Around callback: need to handle recursively.  Execution only gets
+              # paused if:
+              # * The block fails and the callback doesn't run on failures OR
+              # * The block succeeds, but after callbacks are disabled (in which
+              #   case a continuation is stored for later execution)
+              return if catch(:cancel) do
+                callback.call(object, context, self) do
+                  before(complete, index, &block)
+                  
+                  pause if @success && !complete
+                  throw :cancel, true unless @success
+                end
+              end
+            else
+              # Normal before callback
+              callback.call(object, context, self)
+            end
+          end
+          
+          @before_run = true
+        end
+        
+        action = {:success => true}.merge(block_given? ? yield : {})
+        @result, @success = action[:result], action[:success]
+      end
+      
+      # Runs the machine's +after+ callbacks for this transition.  Only
+      # callbacks that are configured to match the event, from state, and to
+      # state will be invoked.
+      # 
+      # Once the callbacks are run, they cannot be run again until this transition
+      # is reset.
+      # 
+      # == Halting
+      # 
+      # If any callback throws a <tt>:halt</tt> exception, it will be caught
+      # and the callback chain will be automatically stopped.  However, this
+      # exception will not bubble up to the caller since +after+ callbacks
+      # should never halt the execution of a +perform+.
+      def after
+        unless @after_run
+          # First resume previously paused callbacks
+          if resume
+            catch(:halt) do
+              type = @success ? :after : :failure
+              machine.callbacks[type].each {|callback| callback.call(object, context, self)}
+            end
+          end
+          
+          @after_run = true
+        end
+      end
+      
+      # Gets a hash of the context defining this unique transition (including
+      # event, from state, and to state).
+      # 
+      # == Example
+      # 
+      #   machine = StateMachine.new(Vehicle)
+      #   transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
+      #   transition.context    # => {:on => :ignite, :from => :parked, :to => :idling}
+      def context
+        @context ||= {:on => event, :from => from_name, :to => to_name}
+      end
+  end
+end
diff --git a/lib/state_machines/transition_collection.rb b/lib/state_machines/transition_collection.rb
new file mode 100644
index 0000000..ff5cbd4
--- /dev/null
+++ b/lib/state_machines/transition_collection.rb
@@ -0,0 +1,246 @@
+module StateMachines
+  # Represents a collection of transitions in a state machine
+  class TransitionCollection < Array
+
+    
+    # Whether to skip running the action for each transition's machine
+    attr_reader :skip_actions
+    
+    # Whether to skip running the after callbacks
+    attr_reader :skip_after
+    
+    # Whether transitions should wrapped around a transaction block
+    attr_reader :use_transactions
+    
+    # Creates a new collection of transitions that can be run in parallel.  Each
+    # transition *must* be for a different attribute.
+    # 
+    # Configuration options:
+    # * <tt>:actions</tt> - Whether to run the action configured for each transition
+    # * <tt>:after</tt> - Whether to run after callbacks
+    # * <tt>:transaction</tt> - Whether to wrap transitions within a transaction
+    def initialize(transitions = [], options = {})
+      super(transitions)
+      
+      # Determine the validity of the transitions as a whole
+      @valid = all?
+      reject! {|transition| !transition}
+      
+      attributes = map {|transition| transition.attribute}.uniq
+      fail ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != length
+
+      options.assert_valid_keys(:actions, :after, :use_transactions)
+      options = {actions: true, after: true, use_transactions: true}.merge(options)
+      @skip_actions = !options[:actions]
+      @skip_after = !options[:after]
+      @use_transactions = options[:use_transactions]
+    end
+    
+    # Runs each of the collection's transitions in parallel.
+    # 
+    # All transitions will run through the following steps:
+    # 1. Before callbacks
+    # 2. Persist state
+    # 3. Invoke action
+    # 4. After callbacks (if configured)
+    # 5. Rollback (if action is unsuccessful)
+    # 
+    # If a block is passed to this method, that block will be called instead
+    # of invoking each transition's action.
+    def perform(&block)
+      reset
+      
+      if valid?
+        if use_event_attributes? && !block_given?
+          each do |transition|
+            transition.transient = true
+            transition.machine.write(object, :event_transition, transition)
+          end
+          
+          run_actions
+        else
+          within_transaction do
+            catch(:halt) { run_callbacks(&block) }
+            rollback unless success?
+          end
+        end
+      end
+      
+      if actions.length == 1 && results.include?(actions.first)
+        results[actions.first]
+      else
+        success?
+      end
+    end
+    
+    protected
+      attr_reader :results #:nodoc:
+      
+    private
+      # Is this a valid set of transitions?  If the collection was creating with
+      # any +false+ values for transitions, then the the collection will be
+      # marked as invalid.
+      def valid?
+        @valid
+      end
+      
+      # Did each transition perform successfully?  This will only be true if the
+      # following requirements are met:
+      # * No +before+ callbacks halt
+      # * All actions run successfully (always true if skipping actions)
+      def success?
+        @success
+      end
+      
+      # Gets the object being transitioned
+      def object
+        first.object
+      end
+      
+      # Gets the list of actions to run.  If configured to skip actions, then
+      # this will return an empty collection.
+      def actions
+        empty? ? [nil] : map {|transition| transition.action}.uniq
+      end
+      
+      # Determines whether an event attribute be used to trigger the transitions
+      # in this collection or whether the transitions be run directly *outside*
+      # of the action.
+      def use_event_attributes?
+        !skip_actions && !skip_after && actions.all? && actions.length == 1 && first.machine.action_hook?
+      end
+      
+      # Resets any information tracked from previous attempts to perform the
+      # collection
+      def reset
+        @results = {}
+        @success = false
+      end
+      
+      # Runs each transition's callbacks recursively.  Once all before callbacks
+      # have been executed, the transitions will then be persisted and the
+      # configured actions will be run.
+      # 
+      # If any transition fails to run its callbacks, :halt will be thrown.
+      def run_callbacks(index = 0, &block)
+        if transition = self[index]
+          throw :halt unless transition.run_callbacks(:after => !skip_after) do
+            run_callbacks(index + 1, &block)
+            {:result => results[transition.action], :success => success?}
+          end
+        else
+          persist
+          run_actions(&block)
+        end
+      end
+      
+      # Transitions the current value of the object's states to those specified by
+      # each transition
+      def persist
+        each {|transition| transition.persist}
+      end
+      
+      # Runs the actions for each transition.  If a block is given method, then it
+      # will be called instead of invoking each transition's action.
+      # 
+      # The results of the actions will be used to determine #success?.
+      def run_actions
+        catch_exceptions do
+          @success = if block_given?
+            result = yield
+            actions.each {|action| results[action] = result}
+            !!result
+          else
+            actions.compact.each {|action| !skip_actions && results[action] = object.send(action)}
+            results.values.all?
+          end
+        end
+      end
+      
+      # Rolls back changes made to the object's states via each transition
+      def rollback
+        each {|transition| transition.rollback}
+      end
+      
+      # Wraps the given block with a rescue handler so that any exceptions that
+      # occur will automatically result in the transition rolling back any changes
+      # that were made to the object involved.
+      def catch_exceptions
+        begin
+          yield
+        rescue Exception
+          rollback
+          raise
+        end
+      end
+      
+      # Runs a block within a transaction for the object being transitioned.  If
+      # transactions are disabled, then this is a no-op.
+      def within_transaction
+        if use_transactions && !empty?
+          first.within_transaction do
+            yield
+            success?
+          end
+        else
+          yield
+        end
+      end
+  end
+  
+  # Represents a collection of transitions that were generated from attribute-
+  # based events
+  class AttributeTransitionCollection < TransitionCollection
+    def initialize(transitions = [], options = {}) #:nodoc:
+      super(transitions, {use_transactions: false, :actions => false}.merge(options))
+    end
+    
+    private
+      # Hooks into running transition callbacks so that event / event transition
+      # attributes can be properly updated
+      def run_callbacks(index = 0)
+        if index == 0
+          # Clears any traces of the event attribute to prevent it from being
+          # evaluated multiple times if actions are nested
+          each do |transition|
+            transition.machine.write(object, :event, nil)
+            transition.machine.write(object, :event_transition, nil)
+          end
+          
+          # Rollback only if exceptions occur during before callbacks
+          begin
+            super
+          rescue Exception
+            rollback unless @before_run
+            @success = nil  # mimics ActiveRecord.save behavior on rollback
+            raise
+          end
+          
+          # Persists transitions on the object if partial transition was successful.
+          # This allows us to reference them later to complete the transition with
+          # after callbacks.          
+          each {|transition| transition.machine.write(object, :event_transition, transition)} if skip_after && success?
+        else
+          super
+        end
+      end
+      
+      # Tracks that before callbacks have now completed
+      def persist
+        @before_run = true
+        super
+      end
+      
+      # Resets callback tracking
+      def reset
+        super
+        @before_run = false
+      end
+      
+      # Resets the event attribute so it can be re-evaluated if attempted again
+      def rollback
+        super
+        each {|transition| transition.machine.write(object, :event, transition.event) unless transition.transient?}
+      end
+  end
+end
diff --git a/lib/state_machines/version.rb b/lib/state_machines/version.rb
new file mode 100644
index 0000000..f8f0a68
--- /dev/null
+++ b/lib/state_machines/version.rb
@@ -0,0 +1,3 @@
+module StateMachines
+  VERSION = '0.4.0'
+end
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..5bd7d43
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,965 @@
+--- !ruby/object:Gem::Specification
+name: state_machines
+version: !ruby/object:Gem::Version
+  version: 0.4.0
+platform: ruby
+authors:
+- Abdelkader Boudih
+- Aaron Pfeifer
+autorequire: 
+bindir: bin
+cert_chain: []
+date: 2015-06-18 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+  name: bundler
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: 1.7.6
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: 1.7.6
+- !ruby/object:Gem::Dependency
+  name: rake
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: minitest
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '5.4'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '5.4'
+description: Adds support for creating state machines for attributes on any Ruby class
+email:
+- terminale at gmail.com
+- aaron at pluginaweek.org
+executables: []
+extensions: []
+extra_rdoc_files: []
+files:
+- ".gitignore"
+- ".rspec"
+- ".travis.yml"
+- Changelog.md
+- Contributors.md
+- Gemfile
+- LICENSE.txt
+- README.md
+- Rakefile
+- Testing.md
+- lib/state_machines.rb
+- lib/state_machines/assertions.rb
+- lib/state_machines/branch.rb
+- lib/state_machines/callback.rb
+- lib/state_machines/core.rb
+- lib/state_machines/core_ext.rb
+- lib/state_machines/core_ext/class/state_machine.rb
+- lib/state_machines/error.rb
+- lib/state_machines/eval_helpers.rb
+- lib/state_machines/event.rb
+- lib/state_machines/event_collection.rb
+- lib/state_machines/extensions.rb
+- lib/state_machines/helper_module.rb
+- lib/state_machines/integrations.rb
+- lib/state_machines/integrations/base.rb
+- lib/state_machines/machine.rb
+- lib/state_machines/machine_collection.rb
+- lib/state_machines/macro_methods.rb
+- lib/state_machines/matcher.rb
+- lib/state_machines/matcher_helpers.rb
+- lib/state_machines/node_collection.rb
+- lib/state_machines/path.rb
+- lib/state_machines/path_collection.rb
+- lib/state_machines/state.rb
+- lib/state_machines/state_collection.rb
+- lib/state_machines/state_context.rb
+- lib/state_machines/transition.rb
+- lib/state_machines/transition_collection.rb
+- lib/state_machines/version.rb
+- state_machines.gemspec
+- test/files/integrations/event_on_failure_integration.rb
+- test/files/integrations/vehicle.rb
+- test/files/models/auto_shop.rb
+- test/files/models/car.rb
+- test/files/models/model_base.rb
+- test/files/models/motorcycle.rb
+- test/files/models/traffic_light.rb
+- test/files/models/vehicle.rb
+- test/files/node.rb
+- test/files/switch.rb
+- test/functional/auto_shop_available_test.rb
+- test/functional/auto_shop_busy_test.rb
+- test/functional/car_backing_up_test.rb
+- test/functional/car_test.rb
+- test/functional/motorcycle_test.rb
+- test/functional/traffic_light_caution_test.rb
+- test/functional/traffic_light_proceed_test.rb
+- test/functional/traffic_light_stop_test.rb
+- test/functional/vehicle_first_gear_test.rb
+- test/functional/vehicle_idling_test.rb
+- test/functional/vehicle_locked_test.rb
+- test/functional/vehicle_parked_test.rb
+- test/functional/vehicle_repaired_test.rb
+- test/functional/vehicle_second_gear_test.rb
+- test/functional/vehicle_stalled_test.rb
+- test/functional/vehicle_test.rb
+- test/functional/vehicle_third_gear_test.rb
+- test/functional/vehicle_unsaved_test.rb
+- test/functional/vehicle_with_event_attributes_test.rb
+- test/functional/vehicle_with_parallel_events_test.rb
+- test/test_helper.rb
+- test/unit/assertions/assert_exclusive_keys_test.rb
+- test/unit/assertions/assert_valid_key_test.rb
+- test/unit/branch/branch_test.rb
+- test/unit/branch/branch_with_conflicting_conditionals_test.rb
+- test/unit/branch/branch_with_conflicting_from_requirements_test.rb
+- test/unit/branch/branch_with_conflicting_on_requirements_test.rb
+- test/unit/branch/branch_with_conflicting_to_requirements_test.rb
+- test/unit/branch/branch_with_different_requirements_test.rb
+- test/unit/branch/branch_with_except_from_matcher_requirement_test.rb
+- test/unit/branch/branch_with_except_from_requirement_test.rb
+- test/unit/branch/branch_with_except_on_matcher_requirement_test.rb
+- test/unit/branch/branch_with_except_on_requirement_test.rb
+- test/unit/branch/branch_with_except_to_matcher_requirement_test.rb
+- test/unit/branch/branch_with_except_to_requirement_test.rb
+- test/unit/branch/branch_with_from_matcher_requirement_test.rb
+- test/unit/branch/branch_with_from_requirement_test.rb
+- test/unit/branch/branch_with_if_conditional_test.rb
+- test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb
+- test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb
+- test/unit/branch/branch_with_implicit_requirement_test.rb
+- test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb
+- test/unit/branch/branch_with_multiple_except_from_requirements_test.rb
+- test/unit/branch/branch_with_multiple_except_on_requirements_test.rb
+- test/unit/branch/branch_with_multiple_except_to_requirements_test.rb
+- test/unit/branch/branch_with_multiple_from_requirements_test.rb
+- test/unit/branch/branch_with_multiple_if_conditionals_test.rb
+- test/unit/branch/branch_with_multiple_implicit_requirements_test.rb
+- test/unit/branch/branch_with_multiple_on_requirements_test.rb
+- test/unit/branch/branch_with_multiple_to_requirements_test.rb
+- test/unit/branch/branch_with_multiple_unless_conditionals_test.rb
+- test/unit/branch/branch_with_nil_requirements_test.rb
+- test/unit/branch/branch_with_no_requirements_test.rb
+- test/unit/branch/branch_with_on_matcher_requirement_test.rb
+- test/unit/branch/branch_with_on_requirement_test.rb
+- test/unit/branch/branch_with_to_matcher_requirement_test.rb
+- test/unit/branch/branch_with_to_requirement_test.rb
+- test/unit/branch/branch_with_unless_conditional_test.rb
+- test/unit/branch/branch_without_guards_test.rb
+- test/unit/callback/callback_by_default_test.rb
+- test/unit/callback/callback_test.rb
+- test/unit/callback/callback_with_application_bound_object_test.rb
+- test/unit/callback/callback_with_application_terminator_test.rb
+- test/unit/callback/callback_with_arguments_test.rb
+- test/unit/callback/callback_with_around_type_and_arguments_test.rb
+- test/unit/callback/callback_with_around_type_and_block_test.rb
+- test/unit/callback/callback_with_around_type_and_bound_method_test.rb
+- test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb
+- test/unit/callback/callback_with_around_type_and_terminator_test.rb
+- test/unit/callback/callback_with_block_test.rb
+- test/unit/callback/callback_with_bound_method_and_arguments_test.rb
+- test/unit/callback/callback_with_bound_method_test.rb
+- test/unit/callback/callback_with_do_method_test.rb
+- test/unit/callback/callback_with_explicit_requirements_test.rb
+- test/unit/callback/callback_with_if_condition_test.rb
+- test/unit/callback/callback_with_implicit_requirements_test.rb
+- test/unit/callback/callback_with_method_argument_test.rb
+- test/unit/callback/callback_with_mixed_methods_test.rb
+- test/unit/callback/callback_with_multiple_bound_methods_test.rb
+- test/unit/callback/callback_with_multiple_do_methods_test.rb
+- test/unit/callback/callback_with_multiple_method_arguments_test.rb
+- test/unit/callback/callback_with_terminator_test.rb
+- test/unit/callback/callback_with_unbound_method_test.rb
+- test/unit/callback/callback_with_unless_condition_test.rb
+- test/unit/callback/callback_without_arguments_test.rb
+- test/unit/callback/callback_without_terminator_test.rb
+- test/unit/error/error_by_default_test.rb
+- test/unit/error/error_with_message_test.rb
+- test/unit/eval_helper/eval_helpers_base_test.rb
+- test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_block_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb
+- test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_string_test.rb
+- test/unit/eval_helper/eval_helpers_string_with_block_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_private_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_protected_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb
+- test/unit/eval_helper/eval_helpers_test.rb
+- test/unit/event/event_after_being_copied_test.rb
+- test/unit/event/event_by_default_test.rb
+- test/unit/event/event_context_test.rb
+- test/unit/event/event_on_failure_test.rb
+- test/unit/event/event_test.rb
+- test/unit/event/event_transitions_test.rb
+- test/unit/event/event_with_conflicting_helpers_after_definition_test.rb
+- test/unit/event/event_with_conflicting_helpers_before_definition_test.rb
+- test/unit/event/event_with_conflicting_machine_test.rb
+- test/unit/event/event_with_dynamic_human_name_test.rb
+- test/unit/event/event_with_human_name_test.rb
+- test/unit/event/event_with_invalid_current_state_test.rb
+- test/unit/event/event_with_machine_action_test.rb
+- test/unit/event/event_with_marshalling_test.rb
+- test/unit/event/event_with_matching_disabled_transitions_test.rb
+- test/unit/event/event_with_matching_enabled_transitions_test.rb
+- test/unit/event/event_with_multiple_transitions_test.rb
+- test/unit/event/event_with_namespace_test.rb
+- test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb
+- test/unit/event/event_with_transition_with_loopback_state_test.rb
+- test/unit/event/event_with_transition_with_nil_to_state_test.rb
+- test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb
+- test/unit/event/event_with_transition_without_to_state_test.rb
+- test/unit/event/event_with_transitions_test.rb
+- test/unit/event/event_without_matching_transitions_test.rb
+- test/unit/event/event_without_transitions_test.rb
+- test/unit/event/invalid_event_test.rb
+- test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb
+- test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb
+- test/unit/event_collection/event_collection_by_default_test.rb
+- test/unit/event_collection/event_collection_test.rb
+- test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb
+- test/unit/event_collection/event_collection_with_events_with_transitions_test.rb
+- test/unit/event_collection/event_collection_with_multiple_events_test.rb
+- test/unit/event_collection/event_collection_with_validations_test.rb
+- test/unit/event_collection/event_collection_without_machine_action_test.rb
+- test/unit/event_collection/event_string_collection_test.rb
+- test/unit/helper_module_test.rb
+- test/unit/integrations/integration_finder_test.rb
+- test/unit/integrations/integration_matcher_test.rb
+- test/unit/invalid_transition/invalid_parallel_transition_test.rb
+- test/unit/invalid_transition/invalid_transition_test.rb
+- test/unit/invalid_transition/invalid_transition_with_integration_test.rb
+- test/unit/invalid_transition/invalid_transition_with_namespace_test.rb
+- test/unit/machine/machine_after_being_copied_test.rb
+- test/unit/machine/machine_after_changing_initial_state.rb
+- test/unit/machine/machine_after_changing_owner_class_test.rb
+- test/unit/machine/machine_by_default_test.rb
+- test/unit/machine/machine_finder_custom_options_test.rb
+- test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb
+- test/unit/machine/machine_finder_with_existing_on_same_class_test.rb
+- test/unit/machine/machine_finder_without_existing_machine_test.rb
+- test/unit/machine/machine_persistence_test.rb
+- test/unit/machine/machine_state_initialization_test.rb
+- test/unit/machine/machine_test.rb
+- test/unit/machine/machine_with_action_already_overridden_test.rb
+- test/unit/machine/machine_with_action_defined_in_class_test.rb
+- test/unit/machine/machine_with_action_defined_in_included_module_test.rb
+- test/unit/machine/machine_with_action_defined_in_superclass_test.rb
+- test/unit/machine/machine_with_action_undefined_test.rb
+- test/unit/machine/machine_with_cached_state_test.rb
+- test/unit/machine/machine_with_class_helpers_test.rb
+- test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb
+- test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb
+- test/unit/machine/machine_with_custom_action_test.rb
+- test/unit/machine/machine_with_custom_attribute_test.rb
+- test/unit/machine/machine_with_custom_initialize_test.rb
+- test/unit/machine/machine_with_custom_integration_test.rb
+- test/unit/machine/machine_with_custom_invalidation_test.rb
+- test/unit/machine/machine_with_custom_name_test.rb
+- test/unit/machine/machine_with_custom_plural_test.rb
+- test/unit/machine/machine_with_dynamic_initial_state_test.rb
+- test/unit/machine/machine_with_event_matchers_test.rb
+- test/unit/machine/machine_with_events_test.rb
+- test/unit/machine/machine_with_events_with_custom_human_names_test.rb
+- test/unit/machine/machine_with_events_with_transitions_test.rb
+- test/unit/machine/machine_with_existing_event_test.rb
+- test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb
+- test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb
+- test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb
+- test/unit/machine/machine_with_existing_state_test.rb
+- test/unit/machine/machine_with_failure_callbacks_test.rb
+- test/unit/machine/machine_with_helpers_test.rb
+- test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb
+- test/unit/machine/machine_with_initialize_and_super_test.rb
+- test/unit/machine/machine_with_initialize_arguments_and_block_test.rb
+- test/unit/machine/machine_with_initialize_without_super_test.rb
+- test/unit/machine/machine_with_instance_helpers_test.rb
+- test/unit/machine/machine_with_integration_test.rb
+- test/unit/machine/machine_with_multiple_events_test.rb
+- test/unit/machine/machine_with_namespace_test.rb
+- test/unit/machine/machine_with_nil_action_test.rb
+- test/unit/machine/machine_with_other_states.rb
+- test/unit/machine/machine_with_owner_subclass_test.rb
+- test/unit/machine/machine_with_paths_test.rb
+- test/unit/machine/machine_with_private_action_test.rb
+- test/unit/machine/machine_with_state_matchers_test.rb
+- test/unit/machine/machine_with_state_with_matchers_test.rb
+- test/unit/machine/machine_with_states_test.rb
+- test/unit/machine/machine_with_states_with_behaviors_test.rb
+- test/unit/machine/machine_with_states_with_custom_human_names_test.rb
+- test/unit/machine/machine_with_states_with_custom_values_test.rb
+- test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb
+- test/unit/machine/machine_with_static_initial_state_test.rb
+- test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb
+- test/unit/machine/machine_with_transition_callbacks_test.rb
+- test/unit/machine/machine_with_transitions_test.rb
+- test/unit/machine/machine_without_initialization_test.rb
+- test/unit/machine/machine_without_initialize_test.rb
+- test/unit/machine/machine_without_integration_test.rb
+- test/unit/machine_collection/machine_collection_by_default_test.rb
+- test/unit/machine_collection/machine_collection_fire_attributes_with_validations_test.rb
+- test/unit/machine_collection/machine_collection_fire_test.rb
+- test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb
+- test/unit/machine_collection/machine_collection_fire_with_validations_test.rb
+- test/unit/machine_collection/machine_collection_state_initialization_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb
+- test/unit/machine_collection/machine_collection_transitions_without_events_test.rb
+- test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb
+- test/unit/matcher/all_matcher_test.rb
+- test/unit/matcher/blacklist_matcher_test.rb
+- test/unit/matcher/loopback_matcher_test.rb
+- test/unit/matcher/matcher_by_default_test.rb
+- test/unit/matcher/matcher_with_multiple_values_test.rb
+- test/unit/matcher/matcher_with_value_test.rb
+- test/unit/matcher/whitelist_matcher_test.rb
+- test/unit/matcher_helpers/matcher_helpers_all_test.rb
+- test/unit/matcher_helpers/matcher_helpers_any_test.rb
+- test/unit/matcher_helpers/matcher_helpers_same_test.rb
+- test/unit/node_collection/node_collection_after_being_copied_test.rb
+- test/unit/node_collection/node_collection_after_update_test.rb
+- test/unit/node_collection/node_collection_by_default_test.rb
+- test/unit/node_collection/node_collection_test.rb
+- test/unit/node_collection/node_collection_with_indices_test.rb
+- test/unit/node_collection/node_collection_with_matcher_contexts_test.rb
+- test/unit/node_collection/node_collection_with_nodes_test.rb
+- test/unit/node_collection/node_collection_with_numeric_index_test.rb
+- test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb
+- test/unit/node_collection/node_collection_with_predefined_contexts_test.rb
+- test/unit/node_collection/node_collection_with_string_index_test.rb
+- test/unit/node_collection/node_collection_with_symbol_index_test.rb
+- test/unit/node_collection/node_collection_without_indices_test.rb
+- test/unit/path/path_by_default_test.rb
+- test/unit/path/path_test.rb
+- test/unit/path/path_with_available_transitions_after_reaching_target_test.rb
+- test/unit/path/path_with_available_transitions_test.rb
+- test/unit/path/path_with_deep_target_reached_test.rb
+- test/unit/path/path_with_deep_target_test.rb
+- test/unit/path/path_with_duplicates_test.rb
+- test/unit/path/path_with_encountered_transitions_test.rb
+- test/unit/path/path_with_guarded_transitions_test.rb
+- test/unit/path/path_with_reached_target_test.rb
+- test/unit/path/path_with_transitions_test.rb
+- test/unit/path/path_with_unreached_target_test.rb
+- test/unit/path/path_without_transitions_test.rb
+- test/unit/path_collection/path_collection_by_default_test.rb
+- test/unit/path_collection/path_collection_test.rb
+- test/unit/path_collection/path_collection_with_deep_paths_test.rb
+- test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb
+- test/unit/path_collection/path_collection_with_from_state_test.rb
+- test/unit/path_collection/path_collection_with_paths_test.rb
+- test/unit/path_collection/path_collection_with_to_state_test.rb
+- test/unit/path_collection/path_with_guarded_paths_test.rb
+- test/unit/state/state_after_being_copied_test.rb
+- test/unit/state/state_by_default_test.rb
+- test/unit/state/state_final_test.rb
+- test/unit/state/state_initial_test.rb
+- test/unit/state/state_not_final_test.rb
+- test/unit/state/state_not_initial_test.rb
+- test/unit/state/state_test.rb
+- test/unit/state/state_with_cached_lambda_value_test.rb
+- test/unit/state/state_with_conflicting_helpers_after_definition_test.rb
+- test/unit/state/state_with_conflicting_helpers_before_definition_test.rb
+- test/unit/state/state_with_conflicting_machine_name_test.rb
+- test/unit/state/state_with_conflicting_machine_test.rb
+- test/unit/state/state_with_context_test.rb
+- test/unit/state/state_with_dynamic_human_name_test.rb
+- test/unit/state/state_with_existing_context_method_test.rb
+- test/unit/state/state_with_human_name_test.rb
+- test/unit/state/state_with_integer_value_test.rb
+- test/unit/state/state_with_invalid_method_call_test.rb
+- test/unit/state/state_with_lambda_value_test.rb
+- test/unit/state/state_with_matcher_test.rb
+- test/unit/state/state_with_multiple_contexts_test.rb
+- test/unit/state/state_with_name_test.rb
+- test/unit/state/state_with_namespace_test.rb
+- test/unit/state/state_with_nil_value_test.rb
+- test/unit/state/state_with_redefined_context_method_test.rb
+- test/unit/state/state_with_symbolic_value_test.rb
+- test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb
+- test/unit/state/state_with_valid_method_call_for_current_state_test.rb
+- test/unit/state/state_with_valid_method_call_for_different_state_test.rb
+- test/unit/state/state_without_cached_lambda_value_test.rb
+- test/unit/state/state_without_name_test.rb
+- test/unit/state_collection/state_collection_by_default_test.rb
+- test/unit/state_collection/state_collection_string_test.rb
+- test/unit/state_collection/state_collection_test.rb
+- test/unit/state_collection/state_collection_with_custom_state_values_test.rb
+- test/unit/state_collection/state_collection_with_event_transitions_test.rb
+- test/unit/state_collection/state_collection_with_initial_state_test.rb
+- test/unit/state_collection/state_collection_with_namespace_test.rb
+- test/unit/state_collection/state_collection_with_state_behaviors_test.rb
+- test/unit/state_collection/state_collection_with_state_matchers_test.rb
+- test/unit/state_collection/state_collection_with_transition_callbacks_test.rb
+- test/unit/state_context/state_context_proxy_test.rb
+- test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb
+- test/unit/state_context/state_context_proxy_with_if_condition_test.rb
+- test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb
+- test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb
+- test/unit/state_context/state_context_proxy_with_unless_condition_test.rb
+- test/unit/state_context/state_context_proxy_without_conditions_test.rb
+- test/unit/state_context/state_context_test.rb
+- test/unit/state_context/state_context_transition_test.rb
+- test/unit/state_context/state_context_with_matching_transition_test.rb
+- test/unit/state_machine/state_machine_by_default_test.rb
+- test/unit/state_machine/state_machine_test.rb
+- test/unit/transition/transition_after_being_performed_test.rb
+- test/unit/transition/transition_after_being_persisted_test.rb
+- test/unit/transition/transition_after_being_rolled_back_test.rb
+- test/unit/transition/transition_equality_test.rb
+- test/unit/transition/transition_loopback_test.rb
+- test/unit/transition/transition_test.rb
+- test/unit/transition/transition_transient_test.rb
+- test/unit/transition/transition_with_action_test.rb
+- test/unit/transition/transition_with_after_callbacks_skipped_test.rb
+- test/unit/transition/transition_with_after_callbacks_test.rb
+- test/unit/transition/transition_with_around_callbacks_test.rb
+- test/unit/transition/transition_with_before_callbacks_skipped_test.rb
+- test/unit/transition/transition_with_before_callbacks_test.rb
+- test/unit/transition/transition_with_custom_machine_attribute_test.rb
+- test/unit/transition/transition_with_different_states_test.rb
+- test/unit/transition/transition_with_dynamic_to_value_test.rb
+- test/unit/transition/transition_with_failure_callbacks_test.rb
+- test/unit/transition/transition_with_invalid_nodes_test.rb
+- test/unit/transition/transition_with_mixed_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_after_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_around_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_before_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_failure_callbacks_test.rb
+- test/unit/transition/transition_with_namespace_test.rb
+- test/unit/transition/transition_with_perform_arguments_test.rb
+- test/unit/transition/transition_with_transactions_test.rb
+- test/unit/transition/transition_without_callbacks_test.rb
+- test/unit/transition/transition_without_reading_state_test.rb
+- test/unit/transition/transition_without_running_action_test.rb
+- test/unit/transition_collection/attribute_transition_collection_by_default_test.rb
+- test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_events_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_by_default_test.rb
+- test/unit/transition_collection/transition_collection_empty_with_block_test.rb
+- test/unit/transition_collection/transition_collection_empty_without_block_test.rb
+- test/unit/transition_collection/transition_collection_invalid_test.rb
+- test/unit/transition_collection/transition_collection_partial_invalid_test.rb
+- test/unit/transition_collection/transition_collection_test.rb
+- test/unit/transition_collection/transition_collection_valid_test.rb
+- test/unit/transition_collection/transition_collection_with_action_error_test.rb
+- test/unit/transition_collection/transition_collection_with_action_failed_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb
+- test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb
+- test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb
+- test/unit/transition_collection/transition_collection_with_block_test.rb
+- test/unit/transition_collection/transition_collection_with_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_different_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_empty_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_transactions_test.rb
+- test/unit/transition_collection/transition_collection_without_transactions_test.rb
+homepage: https://github.com/state-machines/state_machines
+licenses:
+- MIT
+metadata: {}
+post_install_message: 
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+  requirements:
+  - - ">="
+    - !ruby/object:Gem::Version
+      version: 1.9.3
+required_rubygems_version: !ruby/object:Gem::Requirement
+  requirements:
+  - - ">="
+    - !ruby/object:Gem::Version
+      version: '0'
+requirements: []
+rubyforge_project: 
+rubygems_version: 2.4.5
+signing_key: 
+specification_version: 4
+summary: State machines for attributes
+test_files:
+- test/files/integrations/event_on_failure_integration.rb
+- test/files/integrations/vehicle.rb
+- test/files/models/auto_shop.rb
+- test/files/models/car.rb
+- test/files/models/model_base.rb
+- test/files/models/motorcycle.rb
+- test/files/models/traffic_light.rb
+- test/files/models/vehicle.rb
+- test/files/node.rb
+- test/files/switch.rb
+- test/functional/auto_shop_available_test.rb
+- test/functional/auto_shop_busy_test.rb
+- test/functional/car_backing_up_test.rb
+- test/functional/car_test.rb
+- test/functional/motorcycle_test.rb
+- test/functional/traffic_light_caution_test.rb
+- test/functional/traffic_light_proceed_test.rb
+- test/functional/traffic_light_stop_test.rb
+- test/functional/vehicle_first_gear_test.rb
+- test/functional/vehicle_idling_test.rb
+- test/functional/vehicle_locked_test.rb
+- test/functional/vehicle_parked_test.rb
+- test/functional/vehicle_repaired_test.rb
+- test/functional/vehicle_second_gear_test.rb
+- test/functional/vehicle_stalled_test.rb
+- test/functional/vehicle_test.rb
+- test/functional/vehicle_third_gear_test.rb
+- test/functional/vehicle_unsaved_test.rb
+- test/functional/vehicle_with_event_attributes_test.rb
+- test/functional/vehicle_with_parallel_events_test.rb
+- test/test_helper.rb
+- test/unit/assertions/assert_exclusive_keys_test.rb
+- test/unit/assertions/assert_valid_key_test.rb
+- test/unit/branch/branch_test.rb
+- test/unit/branch/branch_with_conflicting_conditionals_test.rb
+- test/unit/branch/branch_with_conflicting_from_requirements_test.rb
+- test/unit/branch/branch_with_conflicting_on_requirements_test.rb
+- test/unit/branch/branch_with_conflicting_to_requirements_test.rb
+- test/unit/branch/branch_with_different_requirements_test.rb
+- test/unit/branch/branch_with_except_from_matcher_requirement_test.rb
+- test/unit/branch/branch_with_except_from_requirement_test.rb
+- test/unit/branch/branch_with_except_on_matcher_requirement_test.rb
+- test/unit/branch/branch_with_except_on_requirement_test.rb
+- test/unit/branch/branch_with_except_to_matcher_requirement_test.rb
+- test/unit/branch/branch_with_except_to_requirement_test.rb
+- test/unit/branch/branch_with_from_matcher_requirement_test.rb
+- test/unit/branch/branch_with_from_requirement_test.rb
+- test/unit/branch/branch_with_if_conditional_test.rb
+- test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb
+- test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb
+- test/unit/branch/branch_with_implicit_requirement_test.rb
+- test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb
+- test/unit/branch/branch_with_multiple_except_from_requirements_test.rb
+- test/unit/branch/branch_with_multiple_except_on_requirements_test.rb
+- test/unit/branch/branch_with_multiple_except_to_requirements_test.rb
+- test/unit/branch/branch_with_multiple_from_requirements_test.rb
+- test/unit/branch/branch_with_multiple_if_conditionals_test.rb
+- test/unit/branch/branch_with_multiple_implicit_requirements_test.rb
+- test/unit/branch/branch_with_multiple_on_requirements_test.rb
+- test/unit/branch/branch_with_multiple_to_requirements_test.rb
+- test/unit/branch/branch_with_multiple_unless_conditionals_test.rb
+- test/unit/branch/branch_with_nil_requirements_test.rb
+- test/unit/branch/branch_with_no_requirements_test.rb
+- test/unit/branch/branch_with_on_matcher_requirement_test.rb
+- test/unit/branch/branch_with_on_requirement_test.rb
+- test/unit/branch/branch_with_to_matcher_requirement_test.rb
+- test/unit/branch/branch_with_to_requirement_test.rb
+- test/unit/branch/branch_with_unless_conditional_test.rb
+- test/unit/branch/branch_without_guards_test.rb
+- test/unit/callback/callback_by_default_test.rb
+- test/unit/callback/callback_test.rb
+- test/unit/callback/callback_with_application_bound_object_test.rb
+- test/unit/callback/callback_with_application_terminator_test.rb
+- test/unit/callback/callback_with_arguments_test.rb
+- test/unit/callback/callback_with_around_type_and_arguments_test.rb
+- test/unit/callback/callback_with_around_type_and_block_test.rb
+- test/unit/callback/callback_with_around_type_and_bound_method_test.rb
+- test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb
+- test/unit/callback/callback_with_around_type_and_terminator_test.rb
+- test/unit/callback/callback_with_block_test.rb
+- test/unit/callback/callback_with_bound_method_and_arguments_test.rb
+- test/unit/callback/callback_with_bound_method_test.rb
+- test/unit/callback/callback_with_do_method_test.rb
+- test/unit/callback/callback_with_explicit_requirements_test.rb
+- test/unit/callback/callback_with_if_condition_test.rb
+- test/unit/callback/callback_with_implicit_requirements_test.rb
+- test/unit/callback/callback_with_method_argument_test.rb
+- test/unit/callback/callback_with_mixed_methods_test.rb
+- test/unit/callback/callback_with_multiple_bound_methods_test.rb
+- test/unit/callback/callback_with_multiple_do_methods_test.rb
+- test/unit/callback/callback_with_multiple_method_arguments_test.rb
+- test/unit/callback/callback_with_terminator_test.rb
+- test/unit/callback/callback_with_unbound_method_test.rb
+- test/unit/callback/callback_with_unless_condition_test.rb
+- test/unit/callback/callback_without_arguments_test.rb
+- test/unit/callback/callback_without_terminator_test.rb
+- test/unit/error/error_by_default_test.rb
+- test/unit/error/error_with_message_test.rb
+- test/unit/eval_helper/eval_helpers_base_test.rb
+- test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_block_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb
+- test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_string_test.rb
+- test/unit/eval_helper/eval_helpers_string_with_block_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_private_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_protected_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb
+- test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb
+- test/unit/eval_helper/eval_helpers_test.rb
+- test/unit/event/event_after_being_copied_test.rb
+- test/unit/event/event_by_default_test.rb
+- test/unit/event/event_context_test.rb
+- test/unit/event/event_on_failure_test.rb
+- test/unit/event/event_test.rb
+- test/unit/event/event_transitions_test.rb
+- test/unit/event/event_with_conflicting_helpers_after_definition_test.rb
+- test/unit/event/event_with_conflicting_helpers_before_definition_test.rb
+- test/unit/event/event_with_conflicting_machine_test.rb
+- test/unit/event/event_with_dynamic_human_name_test.rb
+- test/unit/event/event_with_human_name_test.rb
+- test/unit/event/event_with_invalid_current_state_test.rb
+- test/unit/event/event_with_machine_action_test.rb
+- test/unit/event/event_with_marshalling_test.rb
+- test/unit/event/event_with_matching_disabled_transitions_test.rb
+- test/unit/event/event_with_matching_enabled_transitions_test.rb
+- test/unit/event/event_with_multiple_transitions_test.rb
+- test/unit/event/event_with_namespace_test.rb
+- test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb
+- test/unit/event/event_with_transition_with_loopback_state_test.rb
+- test/unit/event/event_with_transition_with_nil_to_state_test.rb
+- test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb
+- test/unit/event/event_with_transition_without_to_state_test.rb
+- test/unit/event/event_with_transitions_test.rb
+- test/unit/event/event_without_matching_transitions_test.rb
+- test/unit/event/event_without_transitions_test.rb
+- test/unit/event/invalid_event_test.rb
+- test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb
+- test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb
+- test/unit/event_collection/event_collection_by_default_test.rb
+- test/unit/event_collection/event_collection_test.rb
+- test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb
+- test/unit/event_collection/event_collection_with_events_with_transitions_test.rb
+- test/unit/event_collection/event_collection_with_multiple_events_test.rb
+- test/unit/event_collection/event_collection_with_validations_test.rb
+- test/unit/event_collection/event_collection_without_machine_action_test.rb
+- test/unit/event_collection/event_string_collection_test.rb
+- test/unit/helper_module_test.rb
+- test/unit/integrations/integration_finder_test.rb
+- test/unit/integrations/integration_matcher_test.rb
+- test/unit/invalid_transition/invalid_parallel_transition_test.rb
+- test/unit/invalid_transition/invalid_transition_test.rb
+- test/unit/invalid_transition/invalid_transition_with_integration_test.rb
+- test/unit/invalid_transition/invalid_transition_with_namespace_test.rb
+- test/unit/machine/machine_after_being_copied_test.rb
+- test/unit/machine/machine_after_changing_initial_state.rb
+- test/unit/machine/machine_after_changing_owner_class_test.rb
+- test/unit/machine/machine_by_default_test.rb
+- test/unit/machine/machine_finder_custom_options_test.rb
+- test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb
+- test/unit/machine/machine_finder_with_existing_on_same_class_test.rb
+- test/unit/machine/machine_finder_without_existing_machine_test.rb
+- test/unit/machine/machine_persistence_test.rb
+- test/unit/machine/machine_state_initialization_test.rb
+- test/unit/machine/machine_test.rb
+- test/unit/machine/machine_with_action_already_overridden_test.rb
+- test/unit/machine/machine_with_action_defined_in_class_test.rb
+- test/unit/machine/machine_with_action_defined_in_included_module_test.rb
+- test/unit/machine/machine_with_action_defined_in_superclass_test.rb
+- test/unit/machine/machine_with_action_undefined_test.rb
+- test/unit/machine/machine_with_cached_state_test.rb
+- test/unit/machine/machine_with_class_helpers_test.rb
+- test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb
+- test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb
+- test/unit/machine/machine_with_custom_action_test.rb
+- test/unit/machine/machine_with_custom_attribute_test.rb
+- test/unit/machine/machine_with_custom_initialize_test.rb
+- test/unit/machine/machine_with_custom_integration_test.rb
+- test/unit/machine/machine_with_custom_invalidation_test.rb
+- test/unit/machine/machine_with_custom_name_test.rb
+- test/unit/machine/machine_with_custom_plural_test.rb
+- test/unit/machine/machine_with_dynamic_initial_state_test.rb
+- test/unit/machine/machine_with_event_matchers_test.rb
+- test/unit/machine/machine_with_events_test.rb
+- test/unit/machine/machine_with_events_with_custom_human_names_test.rb
+- test/unit/machine/machine_with_events_with_transitions_test.rb
+- test/unit/machine/machine_with_existing_event_test.rb
+- test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb
+- test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb
+- test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb
+- test/unit/machine/machine_with_existing_state_test.rb
+- test/unit/machine/machine_with_failure_callbacks_test.rb
+- test/unit/machine/machine_with_helpers_test.rb
+- test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb
+- test/unit/machine/machine_with_initialize_and_super_test.rb
+- test/unit/machine/machine_with_initialize_arguments_and_block_test.rb
+- test/unit/machine/machine_with_initialize_without_super_test.rb
+- test/unit/machine/machine_with_instance_helpers_test.rb
+- test/unit/machine/machine_with_integration_test.rb
+- test/unit/machine/machine_with_multiple_events_test.rb
+- test/unit/machine/machine_with_namespace_test.rb
+- test/unit/machine/machine_with_nil_action_test.rb
+- test/unit/machine/machine_with_other_states.rb
+- test/unit/machine/machine_with_owner_subclass_test.rb
+- test/unit/machine/machine_with_paths_test.rb
+- test/unit/machine/machine_with_private_action_test.rb
+- test/unit/machine/machine_with_state_matchers_test.rb
+- test/unit/machine/machine_with_state_with_matchers_test.rb
+- test/unit/machine/machine_with_states_test.rb
+- test/unit/machine/machine_with_states_with_behaviors_test.rb
+- test/unit/machine/machine_with_states_with_custom_human_names_test.rb
+- test/unit/machine/machine_with_states_with_custom_values_test.rb
+- test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb
+- test/unit/machine/machine_with_static_initial_state_test.rb
+- test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb
+- test/unit/machine/machine_with_transition_callbacks_test.rb
+- test/unit/machine/machine_with_transitions_test.rb
+- test/unit/machine/machine_without_initialization_test.rb
+- test/unit/machine/machine_without_initialize_test.rb
+- test/unit/machine/machine_without_integration_test.rb
+- test/unit/machine_collection/machine_collection_by_default_test.rb
+- test/unit/machine_collection/machine_collection_fire_attributes_with_validations_test.rb
+- test/unit/machine_collection/machine_collection_fire_test.rb
+- test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb
+- test/unit/machine_collection/machine_collection_fire_with_validations_test.rb
+- test/unit/machine_collection/machine_collection_state_initialization_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb
+- test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb
+- test/unit/machine_collection/machine_collection_transitions_without_events_test.rb
+- test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb
+- test/unit/matcher/all_matcher_test.rb
+- test/unit/matcher/blacklist_matcher_test.rb
+- test/unit/matcher/loopback_matcher_test.rb
+- test/unit/matcher/matcher_by_default_test.rb
+- test/unit/matcher/matcher_with_multiple_values_test.rb
+- test/unit/matcher/matcher_with_value_test.rb
+- test/unit/matcher/whitelist_matcher_test.rb
+- test/unit/matcher_helpers/matcher_helpers_all_test.rb
+- test/unit/matcher_helpers/matcher_helpers_any_test.rb
+- test/unit/matcher_helpers/matcher_helpers_same_test.rb
+- test/unit/node_collection/node_collection_after_being_copied_test.rb
+- test/unit/node_collection/node_collection_after_update_test.rb
+- test/unit/node_collection/node_collection_by_default_test.rb
+- test/unit/node_collection/node_collection_test.rb
+- test/unit/node_collection/node_collection_with_indices_test.rb
+- test/unit/node_collection/node_collection_with_matcher_contexts_test.rb
+- test/unit/node_collection/node_collection_with_nodes_test.rb
+- test/unit/node_collection/node_collection_with_numeric_index_test.rb
+- test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb
+- test/unit/node_collection/node_collection_with_predefined_contexts_test.rb
+- test/unit/node_collection/node_collection_with_string_index_test.rb
+- test/unit/node_collection/node_collection_with_symbol_index_test.rb
+- test/unit/node_collection/node_collection_without_indices_test.rb
+- test/unit/path/path_by_default_test.rb
+- test/unit/path/path_test.rb
+- test/unit/path/path_with_available_transitions_after_reaching_target_test.rb
+- test/unit/path/path_with_available_transitions_test.rb
+- test/unit/path/path_with_deep_target_reached_test.rb
+- test/unit/path/path_with_deep_target_test.rb
+- test/unit/path/path_with_duplicates_test.rb
+- test/unit/path/path_with_encountered_transitions_test.rb
+- test/unit/path/path_with_guarded_transitions_test.rb
+- test/unit/path/path_with_reached_target_test.rb
+- test/unit/path/path_with_transitions_test.rb
+- test/unit/path/path_with_unreached_target_test.rb
+- test/unit/path/path_without_transitions_test.rb
+- test/unit/path_collection/path_collection_by_default_test.rb
+- test/unit/path_collection/path_collection_test.rb
+- test/unit/path_collection/path_collection_with_deep_paths_test.rb
+- test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb
+- test/unit/path_collection/path_collection_with_from_state_test.rb
+- test/unit/path_collection/path_collection_with_paths_test.rb
+- test/unit/path_collection/path_collection_with_to_state_test.rb
+- test/unit/path_collection/path_with_guarded_paths_test.rb
+- test/unit/state/state_after_being_copied_test.rb
+- test/unit/state/state_by_default_test.rb
+- test/unit/state/state_final_test.rb
+- test/unit/state/state_initial_test.rb
+- test/unit/state/state_not_final_test.rb
+- test/unit/state/state_not_initial_test.rb
+- test/unit/state/state_test.rb
+- test/unit/state/state_with_cached_lambda_value_test.rb
+- test/unit/state/state_with_conflicting_helpers_after_definition_test.rb
+- test/unit/state/state_with_conflicting_helpers_before_definition_test.rb
+- test/unit/state/state_with_conflicting_machine_name_test.rb
+- test/unit/state/state_with_conflicting_machine_test.rb
+- test/unit/state/state_with_context_test.rb
+- test/unit/state/state_with_dynamic_human_name_test.rb
+- test/unit/state/state_with_existing_context_method_test.rb
+- test/unit/state/state_with_human_name_test.rb
+- test/unit/state/state_with_integer_value_test.rb
+- test/unit/state/state_with_invalid_method_call_test.rb
+- test/unit/state/state_with_lambda_value_test.rb
+- test/unit/state/state_with_matcher_test.rb
+- test/unit/state/state_with_multiple_contexts_test.rb
+- test/unit/state/state_with_name_test.rb
+- test/unit/state/state_with_namespace_test.rb
+- test/unit/state/state_with_nil_value_test.rb
+- test/unit/state/state_with_redefined_context_method_test.rb
+- test/unit/state/state_with_symbolic_value_test.rb
+- test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb
+- test/unit/state/state_with_valid_method_call_for_current_state_test.rb
+- test/unit/state/state_with_valid_method_call_for_different_state_test.rb
+- test/unit/state/state_without_cached_lambda_value_test.rb
+- test/unit/state/state_without_name_test.rb
+- test/unit/state_collection/state_collection_by_default_test.rb
+- test/unit/state_collection/state_collection_string_test.rb
+- test/unit/state_collection/state_collection_test.rb
+- test/unit/state_collection/state_collection_with_custom_state_values_test.rb
+- test/unit/state_collection/state_collection_with_event_transitions_test.rb
+- test/unit/state_collection/state_collection_with_initial_state_test.rb
+- test/unit/state_collection/state_collection_with_namespace_test.rb
+- test/unit/state_collection/state_collection_with_state_behaviors_test.rb
+- test/unit/state_collection/state_collection_with_state_matchers_test.rb
+- test/unit/state_collection/state_collection_with_transition_callbacks_test.rb
+- test/unit/state_context/state_context_proxy_test.rb
+- test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb
+- test/unit/state_context/state_context_proxy_with_if_condition_test.rb
+- test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb
+- test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb
+- test/unit/state_context/state_context_proxy_with_unless_condition_test.rb
+- test/unit/state_context/state_context_proxy_without_conditions_test.rb
+- test/unit/state_context/state_context_test.rb
+- test/unit/state_context/state_context_transition_test.rb
+- test/unit/state_context/state_context_with_matching_transition_test.rb
+- test/unit/state_machine/state_machine_by_default_test.rb
+- test/unit/state_machine/state_machine_test.rb
+- test/unit/transition/transition_after_being_performed_test.rb
+- test/unit/transition/transition_after_being_persisted_test.rb
+- test/unit/transition/transition_after_being_rolled_back_test.rb
+- test/unit/transition/transition_equality_test.rb
+- test/unit/transition/transition_loopback_test.rb
+- test/unit/transition/transition_test.rb
+- test/unit/transition/transition_transient_test.rb
+- test/unit/transition/transition_with_action_test.rb
+- test/unit/transition/transition_with_after_callbacks_skipped_test.rb
+- test/unit/transition/transition_with_after_callbacks_test.rb
+- test/unit/transition/transition_with_around_callbacks_test.rb
+- test/unit/transition/transition_with_before_callbacks_skipped_test.rb
+- test/unit/transition/transition_with_before_callbacks_test.rb
+- test/unit/transition/transition_with_custom_machine_attribute_test.rb
+- test/unit/transition/transition_with_different_states_test.rb
+- test/unit/transition/transition_with_dynamic_to_value_test.rb
+- test/unit/transition/transition_with_failure_callbacks_test.rb
+- test/unit/transition/transition_with_invalid_nodes_test.rb
+- test/unit/transition/transition_with_mixed_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_after_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_around_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_before_callbacks_test.rb
+- test/unit/transition/transition_with_multiple_failure_callbacks_test.rb
+- test/unit/transition/transition_with_namespace_test.rb
+- test/unit/transition/transition_with_perform_arguments_test.rb
+- test/unit/transition/transition_with_transactions_test.rb
+- test/unit/transition/transition_without_callbacks_test.rb
+- test/unit/transition/transition_without_reading_state_test.rb
+- test/unit/transition/transition_without_running_action_test.rb
+- test/unit/transition_collection/attribute_transition_collection_by_default_test.rb
+- test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_events_test.rb
+- test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_by_default_test.rb
+- test/unit/transition_collection/transition_collection_empty_with_block_test.rb
+- test/unit/transition_collection/transition_collection_empty_without_block_test.rb
+- test/unit/transition_collection/transition_collection_invalid_test.rb
+- test/unit/transition_collection/transition_collection_partial_invalid_test.rb
+- test/unit/transition_collection/transition_collection_test.rb
+- test/unit/transition_collection/transition_collection_valid_test.rb
+- test/unit/transition_collection/transition_collection_with_action_error_test.rb
+- test/unit/transition_collection/transition_collection_with_action_failed_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb
+- test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb
+- test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb
+- test/unit/transition_collection/transition_collection_with_block_test.rb
+- test/unit/transition_collection/transition_collection_with_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_different_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_empty_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb
+- test/unit/transition_collection/transition_collection_with_transactions_test.rb
+- test/unit/transition_collection/transition_collection_without_transactions_test.rb
+has_rdoc: 
diff --git a/state_machines.gemspec b/state_machines.gemspec
new file mode 100644
index 0000000..66bf767
--- /dev/null
+++ b/state_machines.gemspec
@@ -0,0 +1,23 @@
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'state_machines/version'
+
+Gem::Specification.new do |spec|
+  spec.name          = 'state_machines'
+  spec.version       = StateMachines::VERSION
+  spec.authors       = ['Abdelkader Boudih', 'Aaron Pfeifer']
+  spec.email         = %w(terminale at gmail.com aaron at pluginaweek.org)
+  spec.summary       = %q(State machines for attributes)
+  spec.description   = %q(Adds support for creating state machines for attributes on any Ruby class)
+  spec.homepage      = 'https://github.com/state-machines/state_machines'
+  spec.license       = 'MIT'
+
+  spec.required_ruby_version     = '>= 1.9.3'
+  spec.files         = `git ls-files -z`.split("\x0")
+  spec.test_files    = spec.files.grep(/^test\//)
+  spec.require_paths = ['lib']
+
+  spec.add_development_dependency 'bundler', '>= 1.7.6'
+  spec.add_development_dependency 'rake'
+  spec.add_development_dependency 'minitest', '>= 5.4'
+end
diff --git a/test/files/integrations/event_on_failure_integration.rb b/test/files/integrations/event_on_failure_integration.rb
new file mode 100644
index 0000000..606de58
--- /dev/null
+++ b/test/files/integrations/event_on_failure_integration.rb
@@ -0,0 +1,10 @@
+module EventOnFailureIntegration
+  include StateMachines::Integrations::Base
+  def invalidate(object, _attribute, message, values = [])
+    (object.errors ||= []) << generate_message(message, values)
+  end
+
+  def reset(object)
+    object.errors = []
+  end
+end
\ No newline at end of file
diff --git a/test/files/integrations/vehicle.rb b/test/files/integrations/vehicle.rb
new file mode 100644
index 0000000..c0ce7ed
--- /dev/null
+++ b/test/files/integrations/vehicle.rb
@@ -0,0 +1,7 @@
+module VehicleIntegration
+  include StateMachines::Integrations::Base
+
+  def self.matching_ancestors
+    ['Vehicle']
+  end
+end
\ No newline at end of file
diff --git a/test/files/models/auto_shop.rb b/test/files/models/auto_shop.rb
new file mode 100644
index 0000000..2125a22
--- /dev/null
+++ b/test/files/models/auto_shop.rb
@@ -0,0 +1,31 @@
+class AutoShop
+  attr_accessor :num_customers
+
+  def initialize
+    @num_customers = 0
+    super
+  end
+
+  state_machine initial: :available do
+    after_transition available: any, do: :increment_customers
+    after_transition busy: any, do: :decrement_customers
+
+    event :tow_vehicle do
+      transition available: :busy
+    end
+
+    event :fix_vehicle do
+      transition busy: :available
+    end
+  end
+
+  # Increments the number of customers in service
+  def increment_customers
+    self.num_customers += 1
+  end
+
+  # Decrements the number of customers in service
+  def decrement_customers
+    self.num_customers -= 1
+  end
+end
diff --git a/test/files/models/car.rb b/test/files/models/car.rb
new file mode 100644
index 0000000..44e3c9f
--- /dev/null
+++ b/test/files/models/car.rb
@@ -0,0 +1,21 @@
+require_relative '../../files/models/vehicle'
+
+class Car < Vehicle
+  state_machine do
+    event :reverse do
+      transition [:parked, :idling, :first_gear] => :backing_up
+    end
+
+    event :park do
+      transition backing_up: :parked
+    end
+
+    event :idle do
+      transition backing_up: :idling
+    end
+
+    event :shift_up do
+      transition backing_up: :first_gear
+    end
+  end
+end
diff --git a/test/files/models/model_base.rb b/test/files/models/model_base.rb
new file mode 100644
index 0000000..5f5bf35
--- /dev/null
+++ b/test/files/models/model_base.rb
@@ -0,0 +1,6 @@
+class ModelBase
+  def save
+    @saved = true
+    self
+  end
+end
diff --git a/test/files/models/motorcycle.rb b/test/files/models/motorcycle.rb
new file mode 100644
index 0000000..07439e6
--- /dev/null
+++ b/test/files/models/motorcycle.rb
@@ -0,0 +1,11 @@
+require_relative '../../files/models/vehicle'
+
+class Motorcycle < Vehicle
+  state_machine initial: :idling do
+    state :first_gear do
+      def decibels
+        1.0
+      end
+    end
+  end
+end
diff --git a/test/files/models/traffic_light.rb b/test/files/models/traffic_light.rb
new file mode 100644
index 0000000..0dc1dc2
--- /dev/null
+++ b/test/files/models/traffic_light.rb
@@ -0,0 +1,47 @@
+class TrafficLight
+  state_machine initial: :stop do
+    event :cycle do
+      transition stop: :proceed, proceed: :caution, caution: :stop
+    end
+
+    state :stop do
+      def color(transform)
+        value = 'red'
+
+        if block_given?
+          yield value
+        else
+          value.send(transform)
+        end
+
+        value
+      end
+    end
+
+    state all - :proceed do
+      def capture_violations?
+        true
+      end
+    end
+
+    state :proceed do
+      def color(_transform)
+        'green'
+      end
+
+      def capture_violations?
+        false
+      end
+    end
+
+    state :caution do
+      def color(_transform)
+        'yellow'
+      end
+    end
+  end
+
+  def color(transform = :to_s)
+    super
+  end
+end
diff --git a/test/files/models/vehicle.rb b/test/files/models/vehicle.rb
new file mode 100644
index 0000000..b7263d3
--- /dev/null
+++ b/test/files/models/vehicle.rb
@@ -0,0 +1,127 @@
+require_relative 'model_base'
+require_relative 'auto_shop'
+
+class Vehicle < ModelBase
+  attr_accessor :auto_shop, :seatbelt_on, :insurance_premium, :force_idle, :callbacks, :saved, :time_elapsed, :last_transition_args
+
+  def initialize(attributes = {})
+    attributes = {
+      auto_shop: AutoShop.new,
+      seatbelt_on: false,
+      insurance_premium: 50,
+      force_idle: false,
+      callbacks: [],
+      saved: false
+    }.merge(attributes)
+
+    attributes.each { |attr, value| send("#{attr}=", value) }
+    super()
+  end
+
+  # Defines the state machine for the state of the vehicled
+  state_machine initial: ->(vehicle) { vehicle.force_idle ? :idling : :parked }, action: :save do
+    before_transition { |vehicle, transition| vehicle.last_transition_args = transition.args }
+    before_transition parked: any, do: :put_on_seatbelt
+    before_transition any => :stalled, :do => :increase_insurance_premium
+    after_transition any => :parked, :do => lambda { |vehicle| vehicle.seatbelt_on = false }
+    after_transition on: :crash, do: :tow
+    after_transition on: :repair, do: :fix
+
+    # Callback tracking for initial state callbacks
+    after_transition any => :parked, :do => ->(vehicle) { vehicle.callbacks << 'before_enter_parked' }
+    before_transition any => :idling, :do => ->(vehicle) { vehicle.callbacks << 'before_enter_idling' }
+
+    around_transition do |vehicle, _transition, block|
+      time = Time.now
+      block.call
+      vehicle.time_elapsed = Time.now - time
+    end
+
+    event all do
+      transition locked: :parked
+    end
+
+    event :park do
+      transition [:idling, :first_gear] => :parked
+    end
+
+    event :ignite do
+      transition stalled: :stalled
+      transition parked: :idling
+    end
+
+    event :idle do
+      transition first_gear: :idling
+    end
+
+    event :shift_up do
+      transition idling: :first_gear, first_gear: :second_gear, second_gear: :third_gear
+    end
+
+    event :shift_down do
+      transition third_gear: :second_gear
+      transition second_gear: :first_gear
+    end
+
+    event :crash do
+      transition [:first_gear, :second_gear, :third_gear] => :stalled, :if => ->(vehicle) { vehicle.auto_shop.available? }
+    end
+
+    event :repair do
+      transition stalled: :parked, if: :auto_shop_busy?
+    end
+  end
+
+  state_machine :insurance_state, initial: :inactive, namespace: 'insurance' do
+    event :buy do
+      transition inactive: :active
+    end
+
+    event :cancel do
+      transition active: :inactive
+    end
+  end
+
+  def save
+    super
+  end
+
+  def new_record?
+    @saved == false
+  end
+
+  def park
+    super
+  end
+
+  # Tows the vehicle to the auto shop
+  def tow
+    auto_shop.tow_vehicle
+  end
+
+  # Fixes the vehicle; it will no longer be in the auto shop
+  def fix
+    auto_shop.fix_vehicle
+  end
+
+  def decibels
+    0.0
+  end
+
+  private
+
+  # Safety first! Puts on our seatbelt
+  def put_on_seatbelt
+    self.seatbelt_on = true
+  end
+
+  # We crashed! Increase the insurance premium on the vehicle
+  def increase_insurance_premium
+    self.insurance_premium += 100
+  end
+
+  # Is the auto shop currently servicing another customer?
+  def auto_shop_busy?
+    auto_shop.busy?
+  end
+end
diff --git a/test/files/node.rb b/test/files/node.rb
new file mode 100644
index 0000000..5014877
--- /dev/null
+++ b/test/files/node.rb
@@ -0,0 +1,5 @@
+class Node < Struct.new(:name, :value, :machine)
+  def context
+    yield
+  end
+end
diff --git a/test/files/switch.rb b/test/files/switch.rb
new file mode 100644
index 0000000..c715434
--- /dev/null
+++ b/test/files/switch.rb
@@ -0,0 +1,15 @@
+class Switch
+  def self.name
+    @name ||= "Switch_#{rand(1_000_000)}"
+  end
+
+  state_machine do
+    event :turn_on do
+      transition all => :on
+    end
+
+    event :turn_off do
+      transition all => :off
+    end
+  end
+end
diff --git a/test/functional/auto_shop_available_test.rb b/test/functional/auto_shop_available_test.rb
new file mode 100644
index 0000000..f327043
--- /dev/null
+++ b/test/functional/auto_shop_available_test.rb
@@ -0,0 +1,20 @@
+require_relative '../test_helper'
+require_relative '../files/models/auto_shop'
+
+class AutoShopAvailableTest < MiniTest::Test
+  def setup
+    @auto_shop = AutoShop.new
+  end
+
+  def test_should_be_in_available_state
+    assert_equal 'available', @auto_shop.state
+  end
+
+  def test_should_allow_tow_vehicle
+    assert @auto_shop.tow_vehicle
+  end
+
+  def test_should_not_allow_fix_vehicle
+    refute @auto_shop.fix_vehicle
+  end
+end
diff --git a/test/functional/auto_shop_busy_test.rb b/test/functional/auto_shop_busy_test.rb
new file mode 100644
index 0000000..f2d7b0b
--- /dev/null
+++ b/test/functional/auto_shop_busy_test.rb
@@ -0,0 +1,25 @@
+require_relative '../test_helper'
+require_relative '../files/models/auto_shop'
+
+class AutoShopBusyTest < MiniTest::Test
+  def setup
+    @auto_shop = AutoShop.new
+    @auto_shop.tow_vehicle
+  end
+
+  def test_should_be_in_busy_state
+    assert_equal 'busy', @auto_shop.state
+  end
+
+  def test_should_have_incremented_number_of_customers
+    assert_equal 1, @auto_shop.num_customers
+  end
+
+  def test_should_not_allow_tow_vehicle
+    refute @auto_shop.tow_vehicle
+  end
+
+  def test_should_allow_fix_vehicle
+    assert @auto_shop.fix_vehicle
+  end
+end
diff --git a/test/functional/car_backing_up_test.rb b/test/functional/car_backing_up_test.rb
new file mode 100644
index 0000000..3b0cc69
--- /dev/null
+++ b/test/functional/car_backing_up_test.rb
@@ -0,0 +1,45 @@
+require_relative '../test_helper'
+require_relative '../files/models/car'
+
+class CarBackingUpTest < MiniTest::Test
+  def setup
+    @car = Car.new
+    @car.reverse
+  end
+
+  def test_should_be_in_backing_up_state
+    assert_equal 'backing_up', @car.state
+  end
+
+  def test_should_allow_park
+    assert @car.park
+  end
+
+  def test_should_not_allow_ignite
+    refute @car.ignite
+  end
+
+  def test_should_allow_idle
+    assert @car.idle
+  end
+
+  def test_should_allow_shift_up
+    assert @car.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @car.shift_down
+  end
+
+  def test_should_not_allow_crash
+    refute @car.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @car.repair
+  end
+
+  def test_should_not_allow_reverse
+    refute @car.reverse
+  end
+end
diff --git a/test/functional/car_test.rb b/test/functional/car_test.rb
new file mode 100644
index 0000000..b4fb4ec
--- /dev/null
+++ b/test/functional/car_test.rb
@@ -0,0 +1,49 @@
+require_relative '../test_helper'
+require_relative '../files/models/car'
+
+class CarTest < MiniTest::Test
+  def setup
+    @car = Car.new
+  end
+
+  def test_should_be_in_parked_state
+    assert_equal 'parked', @car.state
+  end
+
+  def test_should_not_have_the_seatbelt_on
+    refute @car.seatbelt_on
+  end
+
+  def test_should_not_allow_park
+    refute @car.park
+  end
+
+  def test_should_allow_ignite
+    assert @car.ignite
+    assert_equal 'idling', @car.state
+  end
+
+  def test_should_not_allow_idle
+    refute @car.idle
+  end
+
+  def test_should_not_allow_shift_up
+    refute @car.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @car.shift_down
+  end
+
+  def test_should_not_allow_crash
+    refute @car.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @car.repair
+  end
+
+  def test_should_allow_reverse
+    assert @car.reverse
+  end
+end
diff --git a/test/functional/motorcycle_test.rb b/test/functional/motorcycle_test.rb
new file mode 100644
index 0000000..c97e610
--- /dev/null
+++ b/test/functional/motorcycle_test.rb
@@ -0,0 +1,46 @@
+require_relative '../test_helper'
+require_relative '../files/models/motorcycle'
+
+class MotorcycleTest < MiniTest::Test
+  def setup
+    @motorcycle = Motorcycle.new
+  end
+
+  def test_should_be_in_idling_state
+    assert_equal 'idling', @motorcycle.state
+  end
+
+  def test_should_allow_park
+    assert @motorcycle.park
+  end
+
+  def test_should_not_allow_ignite
+    refute @motorcycle.ignite
+  end
+
+  def test_should_allow_shift_up
+    assert @motorcycle.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @motorcycle.shift_down
+  end
+
+  def test_should_not_allow_crash
+    refute @motorcycle.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @motorcycle.repair
+  end
+
+  def test_should_inherit_decibels_from_superclass
+    @motorcycle.park
+    assert_equal 0.0, @motorcycle.decibels
+  end
+
+  def test_should_use_decibels_defined_in_state
+    @motorcycle.shift_up
+    assert_equal 1.0, @motorcycle.decibels
+  end
+end
diff --git a/test/functional/traffic_light_caution_test.rb b/test/functional/traffic_light_caution_test.rb
new file mode 100644
index 0000000..6ad3093
--- /dev/null
+++ b/test/functional/traffic_light_caution_test.rb
@@ -0,0 +1,17 @@
+require_relative '../test_helper'
+require_relative '../files/models/traffic_light'
+
+class TrafficLightCautionTest < MiniTest::Test
+  def setup
+    @light = TrafficLight.new
+    @light.state = 'caution'
+  end
+
+  def test_should_use_caution_color
+    assert_equal 'yellow', @light.color
+  end
+
+  def test_should_use_caution_capture_violations
+    assert_equal true, @light.capture_violations?
+  end
+end
diff --git a/test/functional/traffic_light_proceed_test.rb b/test/functional/traffic_light_proceed_test.rb
new file mode 100644
index 0000000..bc5cdec
--- /dev/null
+++ b/test/functional/traffic_light_proceed_test.rb
@@ -0,0 +1,17 @@
+require_relative '../test_helper'
+require_relative '../files/models/traffic_light'
+
+class TrafficLightProceedTest < MiniTest::Test
+  def setup
+    @light = TrafficLight.new
+    @light.state = 'proceed'
+  end
+
+  def test_should_use_proceed_color
+    assert_equal 'green', @light.color
+  end
+
+  def test_should_use_proceed_capture_violations
+    assert_equal false, @light.capture_violations?
+  end
+end
diff --git a/test/functional/traffic_light_stop_test.rb b/test/functional/traffic_light_stop_test.rb
new file mode 100644
index 0000000..f4d72dc
--- /dev/null
+++ b/test/functional/traffic_light_stop_test.rb
@@ -0,0 +1,26 @@
+require_relative '../test_helper'
+require_relative '../files/models/traffic_light'
+
+class TrafficLightStopTest < MiniTest::Test
+  def setup
+    @light = TrafficLight.new
+    @light.state = 'stop'
+  end
+
+  def test_should_use_stop_color
+    assert_equal 'red', @light.color
+  end
+
+  def test_should_pass_arguments_through
+    assert_equal 'RED', @light.color(:upcase!)
+  end
+
+  def test_should_pass_block_through
+    color = @light.color { |value| value.upcase! }
+    assert_equal 'RED', color
+  end
+
+  def test_should_use_stop_capture_violations
+    assert_equal true, @light.capture_violations?
+  end
+end
diff --git a/test/functional/vehicle_first_gear_test.rb b/test/functional/vehicle_first_gear_test.rb
new file mode 100644
index 0000000..31e2574
--- /dev/null
+++ b/test/functional/vehicle_first_gear_test.rb
@@ -0,0 +1,42 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleFirstGearTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.ignite
+    @vehicle.shift_up
+  end
+
+  def test_should_be_in_first_gear_state
+    assert_equal 'first_gear', @vehicle.state
+  end
+
+  def test_should_be_first_gear
+    assert @vehicle.first_gear?
+  end
+
+  def test_should_allow_park
+    assert @vehicle.park
+  end
+
+  def test_should_allow_idle
+    assert @vehicle.idle
+  end
+
+  def test_should_allow_shift_up
+    assert @vehicle.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @vehicle.shift_down
+  end
+
+  def test_should_allow_crash
+    assert @vehicle.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @vehicle.repair
+  end
+end
diff --git a/test/functional/vehicle_idling_test.rb b/test/functional/vehicle_idling_test.rb
new file mode 100644
index 0000000..8ea378f
--- /dev/null
+++ b/test/functional/vehicle_idling_test.rb
@@ -0,0 +1,59 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleIdlingTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.ignite
+  end
+
+  def test_should_be_in_idling_state
+    assert_equal 'idling', @vehicle.state
+  end
+
+  def test_should_be_idling
+    assert @vehicle.idling?
+  end
+
+  def test_should_have_seatbelt_on
+    assert @vehicle.seatbelt_on
+  end
+
+  def test_should_track_time_elapsed
+    refute_nil @vehicle.time_elapsed
+  end
+
+  def test_should_allow_park
+    assert @vehicle.park
+  end
+
+  def test_should_call_park_with_bang_action
+    class << @vehicle
+      def park
+        super && 1
+      end
+    end
+
+    assert_equal 1, @vehicle.park!
+  end
+
+  def test_should_not_allow_idle
+    refute @vehicle.idle
+  end
+
+  def test_should_allow_shift_up
+    assert @vehicle.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @vehicle.shift_down
+  end
+
+  def test_should_not_allow_crash
+    refute @vehicle.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @vehicle.repair
+  end
+end
diff --git a/test/functional/vehicle_locked_test.rb b/test/functional/vehicle_locked_test.rb
new file mode 100644
index 0000000..8b6a5ee
--- /dev/null
+++ b/test/functional/vehicle_locked_test.rb
@@ -0,0 +1,29 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleLockedTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.state = 'locked'
+  end
+
+  def test_should_be_parked_after_park
+    @vehicle.park
+    assert @vehicle.parked?
+  end
+
+  def test_should_be_parked_after_ignite
+    @vehicle.ignite
+    assert @vehicle.parked?
+  end
+
+  def test_should_be_parked_after_shift_up
+    @vehicle.shift_up
+    assert @vehicle.parked?
+  end
+
+  def test_should_be_parked_after_shift_down
+    @vehicle.shift_down
+    assert @vehicle.parked?
+  end
+end
diff --git a/test/functional/vehicle_parked_test.rb b/test/functional/vehicle_parked_test.rb
new file mode 100644
index 0000000..1e73924
--- /dev/null
+++ b/test/functional/vehicle_parked_test.rb
@@ -0,0 +1,53 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleParkedTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+  end
+
+  def test_should_be_in_parked_state
+    assert_equal 'parked', @vehicle.state
+  end
+
+  def test_should_not_have_the_seatbelt_on
+    refute @vehicle.seatbelt_on
+  end
+
+  def test_should_not_allow_park
+    refute @vehicle.park
+  end
+
+  def test_should_allow_ignite
+    assert @vehicle.ignite
+    assert_equal 'idling', @vehicle.state
+  end
+
+  def test_should_not_allow_idle
+    refute @vehicle.idle
+  end
+
+  def test_should_not_allow_shift_up
+    refute @vehicle.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @vehicle.shift_down
+  end
+
+  def test_should_not_allow_crash
+    refute @vehicle.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @vehicle.repair
+  end
+
+  def test_should_raise_exception_if_repair_not_allowed!
+    exception = assert_raises(StateMachines::InvalidTransition) { @vehicle.repair! }
+    assert_equal @vehicle, exception.object
+    assert_equal Vehicle.state_machine(:state), exception.machine
+    assert_equal :repair, exception.event
+    assert_equal 'parked', exception.from
+  end
+end
diff --git a/test/functional/vehicle_repaired_test.rb b/test/functional/vehicle_repaired_test.rb
new file mode 100644
index 0000000..182d45d
--- /dev/null
+++ b/test/functional/vehicle_repaired_test.rb
@@ -0,0 +1,20 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleRepairedTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.ignite
+    @vehicle.shift_up
+    @vehicle.crash
+    @vehicle.repair
+  end
+
+  def test_should_be_in_parked_state
+    assert_equal 'parked', @vehicle.state
+  end
+
+  def test_should_not_have_a_busy_auto_shop
+    assert @vehicle.auto_shop.available?
+  end
+end
diff --git a/test/functional/vehicle_second_gear_test.rb b/test/functional/vehicle_second_gear_test.rb
new file mode 100644
index 0000000..7dc5157
--- /dev/null
+++ b/test/functional/vehicle_second_gear_test.rb
@@ -0,0 +1,42 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleSecondGearTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.ignite
+    2.times { @vehicle.shift_up }
+  end
+
+  def test_should_be_in_second_gear_state
+    assert_equal 'second_gear', @vehicle.state
+  end
+
+  def test_should_be_second_gear
+    assert @vehicle.second_gear?
+  end
+
+  def test_should_not_allow_park
+    refute @vehicle.park
+  end
+
+  def test_should_not_allow_idle
+    refute @vehicle.idle
+  end
+
+  def test_should_allow_shift_up
+    assert @vehicle.shift_up
+  end
+
+  def test_should_allow_shift_down
+    assert @vehicle.shift_down
+  end
+
+  def test_should_allow_crash
+    assert @vehicle.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @vehicle.repair
+  end
+end
diff --git a/test/functional/vehicle_stalled_test.rb b/test/functional/vehicle_stalled_test.rb
new file mode 100644
index 0000000..3db51dc
--- /dev/null
+++ b/test/functional/vehicle_stalled_test.rb
@@ -0,0 +1,65 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleStalledTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.ignite
+    @vehicle.shift_up
+    @vehicle.crash
+  end
+
+  def test_should_be_in_stalled_state
+    assert_equal 'stalled', @vehicle.state
+  end
+
+  def test_should_be_stalled
+    assert @vehicle.stalled?
+  end
+
+  def test_should_be_towed
+    assert @vehicle.auto_shop.busy?
+    assert_equal 1, @vehicle.auto_shop.num_customers
+  end
+
+  def test_should_have_an_increased_insurance_premium
+    assert_equal 150, @vehicle.insurance_premium
+  end
+
+  def test_should_not_allow_park
+    refute @vehicle.park
+  end
+
+  def test_should_allow_ignite
+    assert @vehicle.ignite
+  end
+
+  def test_should_not_change_state_when_ignited
+    assert_equal 'stalled', @vehicle.state
+  end
+
+  def test_should_not_allow_idle
+    refute @vehicle.idle
+  end
+
+  def test_should_now_allow_shift_up
+    refute @vehicle.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @vehicle.shift_down
+  end
+
+  def test_should_not_allow_crash
+    refute @vehicle.crash
+  end
+
+  def test_should_allow_repair_if_auto_shop_is_busy
+    assert @vehicle.repair
+  end
+
+  def test_should_not_allow_repair_if_auto_shop_is_available
+    @vehicle.auto_shop.fix_vehicle
+    refute @vehicle.repair
+  end
+end
diff --git a/test/functional/vehicle_test.rb b/test/functional/vehicle_test.rb
new file mode 100644
index 0000000..79866e1
--- /dev/null
+++ b/test/functional/vehicle_test.rb
@@ -0,0 +1,20 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+  end
+
+  def test_should_not_allow_access_to_subclass_events
+    refute @vehicle.respond_to?(:reverse)
+  end
+
+  def test_should_have_human_state_names
+    assert_equal 'parked', Vehicle.human_state_name(:parked)
+  end
+
+  def test_should_have_human_state_event_names
+    assert_equal 'park', Vehicle.human_state_event_name(:park)
+  end
+end
diff --git a/test/functional/vehicle_third_gear_test.rb b/test/functional/vehicle_third_gear_test.rb
new file mode 100644
index 0000000..0840e00
--- /dev/null
+++ b/test/functional/vehicle_third_gear_test.rb
@@ -0,0 +1,42 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleThirdGearTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.ignite
+    3.times { @vehicle.shift_up }
+  end
+
+  def test_should_be_in_third_gear_state
+    assert_equal 'third_gear', @vehicle.state
+  end
+
+  def test_should_be_third_gear
+    assert @vehicle.third_gear?
+  end
+
+  def test_should_not_allow_park
+    refute @vehicle.park
+  end
+
+  def test_should_not_allow_idle
+    refute @vehicle.idle
+  end
+
+  def test_should_not_allow_shift_up
+    refute @vehicle.shift_up
+  end
+
+  def test_should_allow_shift_down
+    assert @vehicle.shift_down
+  end
+
+  def test_should_allow_crash
+    assert @vehicle.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @vehicle.repair
+  end
+end
diff --git a/test/functional/vehicle_unsaved_test.rb b/test/functional/vehicle_unsaved_test.rb
new file mode 100644
index 0000000..f990164
--- /dev/null
+++ b/test/functional/vehicle_unsaved_test.rb
@@ -0,0 +1,181 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleUnsavedTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+  end
+
+  def test_should_be_in_parked_state
+    assert_equal 'parked', @vehicle.state
+  end
+
+  def test_should_raise_exception_if_checking_invalid_state
+    assert_raises(IndexError) { @vehicle.state?(:invalid) }
+  end
+
+  def test_should_raise_exception_if_getting_name_of_invalid_state
+    @vehicle.state = 'invalid'
+    assert_raises(ArgumentError) { @vehicle.state_name }
+  end
+
+  def test_should_be_parked
+    assert @vehicle.parked?
+    assert @vehicle.state?(:parked)
+    assert_equal :parked, @vehicle.state_name
+    assert_equal 'parked', @vehicle.human_state_name
+  end
+
+  def test_should_not_be_idling
+    refute @vehicle.idling?
+  end
+
+  def test_should_not_be_first_gear
+    refute @vehicle.first_gear?
+  end
+
+  def test_should_not_be_second_gear
+    refute @vehicle.second_gear?
+  end
+
+  def test_should_not_be_stalled
+    refute @vehicle.stalled?
+  end
+
+  def test_should_not_be_able_to_park
+    refute @vehicle.can_park?
+  end
+
+  def test_should_not_have_a_transition_for_park
+    assert_nil @vehicle.park_transition
+  end
+
+  def test_should_not_allow_park
+    refute @vehicle.park
+  end
+
+  def test_should_be_able_to_ignite
+    assert @vehicle.can_ignite?
+  end
+
+  def test_should_have_a_transition_for_ignite
+    transition = @vehicle.ignite_transition
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'idling', transition.to
+    assert_equal :ignite, transition.event
+    assert_equal :state, transition.attribute
+    assert_equal @vehicle, transition.object
+  end
+
+  def test_should_have_a_list_of_possible_events
+    assert_equal [:ignite], @vehicle.state_events
+  end
+
+  def test_should_have_a_list_of_possible_transitions
+    assert_equal [{ object: @vehicle, attribute: :state, event: :ignite, from: 'parked', to: 'idling' }], @vehicle.state_transitions.map { |transition| transition.attributes }
+  end
+
+  def test_should_have_a_list_of_possible_paths
+    assert_equal [[
+      StateMachines::Transition.new(@vehicle, Vehicle.state_machine, :ignite, :parked, :idling),
+      StateMachines::Transition.new(@vehicle, Vehicle.state_machine, :shift_up, :idling, :first_gear)
+    ]], @vehicle.state_paths(to: :first_gear)
+  end
+
+  def test_should_allow_generic_event_to_fire
+    assert @vehicle.fire_state_event(:ignite)
+    assert_equal 'idling', @vehicle.state
+  end
+
+  def test_should_pass_arguments_through_to_generic_event_runner
+    @vehicle.fire_state_event(:ignite, 1, 2, 3)
+    assert_equal [1, 2, 3], @vehicle.last_transition_args
+  end
+
+  def test_should_allow_skipping_action_through_generic_event_runner
+    @vehicle.fire_state_event(:ignite, false)
+    assert_equal false, @vehicle.saved
+  end
+
+  def test_should_raise_error_with_invalid_event_through_generic_event_runer
+    assert_raises(IndexError) { @vehicle.fire_state_event(:invalid) }
+  end
+
+  def test_should_allow_ignite
+    assert @vehicle.ignite
+    assert_equal 'idling', @vehicle.state
+  end
+
+  def test_should_allow_ignite_with_skipped_action
+    assert @vehicle.ignite(false)
+    assert @vehicle.new_record?
+  end
+
+  def test_should_allow_ignite_bang
+    assert @vehicle.ignite!
+  end
+
+  def test_should_allow_ignite_bang_with_skipped_action
+    assert @vehicle.ignite!(false)
+    assert @vehicle.new_record?
+  end
+
+  def test_should_be_saved_after_successful_event
+    @vehicle.ignite
+    refute @vehicle.new_record?
+  end
+
+  def test_should_not_allow_idle
+    refute @vehicle.idle
+  end
+
+  def test_should_not_allow_shift_up
+    refute @vehicle.shift_up
+  end
+
+  def test_should_not_allow_shift_down
+    refute @vehicle.shift_down
+  end
+
+  def test_should_not_allow_crash
+    refute @vehicle.crash
+  end
+
+  def test_should_not_allow_repair
+    refute @vehicle.repair
+  end
+
+  def test_should_be_insurance_inactive
+    assert @vehicle.insurance_inactive?
+  end
+
+  def test_should_be_able_to_buy
+    assert @vehicle.can_buy_insurance?
+  end
+
+  def test_should_allow_buying_insurance
+    assert @vehicle.buy_insurance
+  end
+
+  def test_should_allow_buying_insurance_bang
+    assert @vehicle.buy_insurance!
+  end
+
+  def test_should_allow_ignite_buying_insurance_with_skipped_action
+    assert @vehicle.buy_insurance!(false)
+    assert @vehicle.new_record?
+  end
+
+  def test_should_not_be_insurance_active
+    refute @vehicle.insurance_active?
+  end
+
+  def test_should_not_be_able_to_cancel
+    refute @vehicle.can_cancel_insurance?
+  end
+
+  def test_should_not_allow_cancelling_insurance
+    refute @vehicle.cancel_insurance
+  end
+end
diff --git a/test/functional/vehicle_with_event_attributes_test.rb b/test/functional/vehicle_with_event_attributes_test.rb
new file mode 100644
index 0000000..e7fe4b2
--- /dev/null
+++ b/test/functional/vehicle_with_event_attributes_test.rb
@@ -0,0 +1,30 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleWithEventAttributesTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+    @vehicle.state_event = 'ignite'
+  end
+
+  def test_should_fail_if_event_is_invalid
+    @vehicle.state_event = 'invalid'
+    refute @vehicle.save
+    assert_equal 'parked', @vehicle.state
+  end
+
+  def test_should_fail_if_event_has_no_transition
+    @vehicle.state_event = 'park'
+    refute @vehicle.save
+    assert_equal 'parked', @vehicle.state
+  end
+
+  def test_should_return_original_action_value_on_success
+    assert_equal @vehicle, @vehicle.save
+  end
+
+  def test_should_transition_state_on_success
+    @vehicle.save
+    assert_equal 'idling', @vehicle.state
+  end
+end
diff --git a/test/functional/vehicle_with_parallel_events_test.rb b/test/functional/vehicle_with_parallel_events_test.rb
new file mode 100644
index 0000000..46fa39f
--- /dev/null
+++ b/test/functional/vehicle_with_parallel_events_test.rb
@@ -0,0 +1,36 @@
+require_relative '../test_helper'
+require_relative '../files/models/vehicle'
+
+class VehicleWithParallelEventsTest < MiniTest::Test
+  def setup
+    @vehicle = Vehicle.new
+  end
+
+  def test_should_fail_if_any_event_cannot_transition
+    refute @vehicle.fire_events(:ignite, :cancel_insurance)
+  end
+
+  def test_should_be_successful_if_all_events_transition
+    assert @vehicle.fire_events(:ignite, :buy_insurance)
+  end
+
+  def test_should_not_save_if_skipping_action
+    assert @vehicle.fire_events(:ignite, :buy_insurance, false)
+    refute @vehicle.saved
+  end
+
+  def test_should_raise_exception_if_any_event_cannot_transition_on_bang
+    exception = assert_raises(StateMachines::InvalidParallelTransition) { @vehicle.fire_events!(:ignite, :cancel_insurance) }
+    assert_equal @vehicle, exception.object
+    assert_equal [:ignite, :cancel_insurance], exception.events
+  end
+
+  def test_should_not_raise_exception_if_all_events_transition_on_bang
+    assert @vehicle.fire_events!(:ignite, :buy_insurance)
+  end
+
+  def test_should_not_save_if_skipping_action_on_bang
+    assert @vehicle.fire_events!(:ignite, :buy_insurance, false)
+    refute @vehicle.saved
+  end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000..336a80b
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,15 @@
+require 'state_machines'
+require 'minitest/autorun'
+begin
+  require 'pry-byebug'
+rescue LoadError
+end
+require 'minitest/reporters'
+Minitest::Reporters.use! [Minitest::Reporters::ProgressReporter.new]
+
+class StateMachinesTest < MiniTest::Test
+  def before_setup
+    super
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/assertions/assert_exclusive_keys_test.rb b/test/unit/assertions/assert_exclusive_keys_test.rb
new file mode 100644
index 0000000..2ab728b
--- /dev/null
+++ b/test/unit/assertions/assert_exclusive_keys_test.rb
@@ -0,0 +1,22 @@
+require_relative '../../test_helper'
+
+class AssertExclusiveKeysTest < StateMachinesTest
+  def test_should_not_raise_exception_if_no_keys_found
+    { on: :park }.assert_exclusive_keys(:only, :except)
+  end
+
+  def test_should_not_raise_exception_if_one_key_found
+    { only: :parked }.assert_exclusive_keys(:only, :except)
+    { except: :parked }.assert_exclusive_keys(:only, :except)
+  end
+
+  def test_should_raise_exception_if_two_keys_found
+    exception = assert_raises(ArgumentError) { { only: :parked, except: :parked }.assert_exclusive_keys(:only, :except) }
+    assert_equal 'Conflicting keys: only, except', exception.message
+  end
+
+  def test_should_raise_exception_if_multiple_keys_found
+    exception = assert_raises(ArgumentError) { { only: :parked, except: :parked, on: :park }.assert_exclusive_keys(:only, :except, :with) }
+    assert_equal 'Conflicting keys: only, except', exception.message
+  end
+end
diff --git a/test/unit/assertions/assert_valid_key_test.rb b/test/unit/assertions/assert_valid_key_test.rb
new file mode 100644
index 0000000..9ee68ed
--- /dev/null
+++ b/test/unit/assertions/assert_valid_key_test.rb
@@ -0,0 +1,12 @@
+require_relative '../../test_helper'
+
+class AssertValidKeysTest < StateMachinesTest
+  def test_should_not_raise_exception_if_key_is_valid
+    { name: 'foo', value: 'bar' }.assert_valid_keys(:name, :value, :force)
+  end
+
+  def test_should_raise_exception_if_key_is_invalid
+    exception = assert_raises(ArgumentError) { { name: 'foo', value: 'bar', invalid: true }.assert_valid_keys(:name, :value, :force) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :name, :value, :force', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_test.rb b/test/unit/branch/branch_test.rb
new file mode 100644
index 0000000..cb39be5
--- /dev/null
+++ b/test/unit/branch/branch_test.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class BranchTest < StateMachinesTest
+  def setup
+    @branch = StateMachines::Branch.new(from: :parked, to: :idling)
+  end
+
+  def test_should_not_raise_exception_if_implicit_option_specified
+    StateMachines::Branch.new(invalid: :valid)
+  end
+
+  def test_should_not_have_an_if_condition
+    assert_nil @branch.if_condition
+  end
+
+  def test_should_not_have_an_unless_condition
+    assert_nil @branch.unless_condition
+  end
+
+  def test_should_have_a_state_requirement
+    assert_equal 1, @branch.state_requirements.length
+  end
+
+  def test_should_raise_an_exception_if_invalid_match_option_specified
+    exception = assert_raises(ArgumentError) { @branch.match(Object.new, invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :from, :to, :on, :guard', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_with_conflicting_conditionals_test.rb b/test/unit/branch/branch_with_conflicting_conditionals_test.rb
new file mode 100644
index 0000000..8064155
--- /dev/null
+++ b/test/unit/branch/branch_with_conflicting_conditionals_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class BranchWithConflictingConditionalsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_match_if_if_is_true_and_unless_is_false
+    branch = StateMachines::Branch.new(if: lambda { true }, unless: lambda { false })
+    assert branch.match(@object)
+  end
+
+  def test_should_not_match_if_if_is_false_and_unless_is_true
+    branch = StateMachines::Branch.new(if: lambda { false }, unless: lambda { true })
+    refute branch.match(@object)
+  end
+
+  def test_should_not_match_if_if_is_false_and_unless_is_false
+    branch = StateMachines::Branch.new(if: lambda { false }, unless: lambda { false })
+    refute branch.match(@object)
+  end
+
+  def test_should_not_match_if_if_is_true_and_unless_is_true
+    branch = StateMachines::Branch.new(if: lambda { true }, unless: lambda { true })
+    refute branch.match(@object)
+  end
+end
diff --git a/test/unit/branch/branch_with_conflicting_from_requirements_test.rb b/test/unit/branch/branch_with_conflicting_from_requirements_test.rb
new file mode 100644
index 0000000..759e10a
--- /dev/null
+++ b/test/unit/branch/branch_with_conflicting_from_requirements_test.rb
@@ -0,0 +1,8 @@
+require_relative '../../test_helper'
+
+class BranchWithConflictingFromRequirementsTest < StateMachinesTest
+  def test_should_raise_an_exception
+    exception = assert_raises(ArgumentError) { StateMachines::Branch.new(from: :parked, except_from: :parked) }
+    assert_equal 'Conflicting keys: from, except_from', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_with_conflicting_on_requirements_test.rb b/test/unit/branch/branch_with_conflicting_on_requirements_test.rb
new file mode 100644
index 0000000..a300909
--- /dev/null
+++ b/test/unit/branch/branch_with_conflicting_on_requirements_test.rb
@@ -0,0 +1,8 @@
+require_relative '../../test_helper'
+
+class BranchWithConflictingOnRequirementsTest < StateMachinesTest
+  def test_should_raise_an_exception
+    exception = assert_raises(ArgumentError) { StateMachines::Branch.new(on: :ignite, except_on: :ignite) }
+    assert_equal 'Conflicting keys: on, except_on', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_with_conflicting_to_requirements_test.rb b/test/unit/branch/branch_with_conflicting_to_requirements_test.rb
new file mode 100644
index 0000000..ff4ca25
--- /dev/null
+++ b/test/unit/branch/branch_with_conflicting_to_requirements_test.rb
@@ -0,0 +1,8 @@
+require_relative '../../test_helper'
+
+class BranchWithConflictingToRequirementsTest < StateMachinesTest
+  def test_should_raise_an_exception
+    exception = assert_raises(ArgumentError) { StateMachines::Branch.new(to: :idling, except_to: :idling) }
+    assert_equal 'Conflicting keys: to, except_to', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_with_different_requirements_test.rb b/test/unit/branch/branch_with_different_requirements_test.rb
new file mode 100644
index 0000000..5bde0d1
--- /dev/null
+++ b/test/unit/branch/branch_with_different_requirements_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class BranchWithDifferentRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(from: :parked, to: :idling, on: :ignite)
+  end
+
+  def test_should_match_empty_query
+    assert @branch.matches?(@object)
+  end
+
+  def test_should_match_if_all_requirements_match
+    assert @branch.matches?(@object, from: :parked, to: :idling, on: :ignite)
+  end
+
+  def test_should_not_match_if_from_not_included
+    refute @branch.matches?(@object, from: :idling)
+  end
+
+  def test_should_not_match_if_to_not_included
+    refute @branch.matches?(@object, to: :parked)
+  end
+
+  def test_should_not_match_if_on_not_included
+    refute @branch.matches?(@object, on: :park)
+  end
+
+  def test_should_be_nil_if_unmatched
+    assert_nil @branch.match(@object, from: :parked, to: :idling, on: :park)
+  end
+
+  def test_should_include_all_known_states
+    assert_equal [:parked, :idling], @branch.known_states
+  end
+
+  def test_should_not_duplicate_known_statse
+    branch = StateMachines::Branch.new(except_from: :idling, to: :idling, on: :ignite)
+    assert_equal [:idling], branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_except_from_matcher_requirement_test.rb b/test/unit/branch/branch_with_except_from_matcher_requirement_test.rb
new file mode 100644
index 0000000..ba07354
--- /dev/null
+++ b/test/unit/branch/branch_with_except_from_matcher_requirement_test.rb
@@ -0,0 +1,8 @@
+require_relative '../../test_helper'
+
+class BranchWithExceptFromMatcherRequirementTest < StateMachinesTest
+  def test_should_raise_an_exception
+    exception = assert_raises(ArgumentError) { StateMachines::Branch.new(except_from: StateMachines::AllMatcher.instance) }
+    assert_equal ':except_from option cannot use matchers; use :from instead', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_with_except_from_requirement_test.rb b/test/unit/branch/branch_with_except_from_requirement_test.rb
new file mode 100644
index 0000000..8332c18
--- /dev/null
+++ b/test/unit/branch/branch_with_except_from_requirement_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class BranchWithExceptFromRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(except_from: :parked)
+  end
+
+  def test_should_use_a_blacklist_matcher
+    assert_instance_of StateMachines::BlacklistMatcher, @branch.state_requirements.first[:from]
+  end
+
+  def test_should_match_if_not_included
+    assert @branch.matches?(@object, from: :idling)
+  end
+
+  def test_should_not_match_if_included
+    refute @branch.matches?(@object, from: :parked)
+  end
+
+  def test_should_match_if_nil
+    assert @branch.matches?(@object, from: nil)
+  end
+
+  def test_should_ignore_to
+    assert @branch.matches?(@object, from: :idling, to: :parked)
+  end
+
+  def test_should_ignore_on
+    assert @branch.matches?(@object, from: :idling, on: :ignite)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_except_on_matcher_requirement_test.rb b/test/unit/branch/branch_with_except_on_matcher_requirement_test.rb
new file mode 100644
index 0000000..0289d3e
--- /dev/null
+++ b/test/unit/branch/branch_with_except_on_matcher_requirement_test.rb
@@ -0,0 +1,8 @@
+require_relative '../../test_helper'
+
+class BranchWithExceptOnMatcherRequirementTest < StateMachinesTest
+  def test_should_raise_an_exception
+    exception = assert_raises(ArgumentError) { StateMachines::Branch.new(except_on: StateMachines::AllMatcher.instance) }
+    assert_equal ':except_on option cannot use matchers; use :on instead', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_with_except_on_requirement_test.rb b/test/unit/branch/branch_with_except_on_requirement_test.rb
new file mode 100644
index 0000000..f0c8b27
--- /dev/null
+++ b/test/unit/branch/branch_with_except_on_requirement_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class BranchWithExceptOnRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(except_on: :ignite)
+  end
+
+  def test_should_use_a_blacklist_matcher
+    assert_instance_of StateMachines::BlacklistMatcher, @branch.event_requirement
+  end
+
+  def test_should_match_if_not_included
+    assert @branch.matches?(@object, on: :park)
+  end
+
+  def test_should_not_match_if_included
+    refute @branch.matches?(@object, on: :ignite)
+  end
+
+  def test_should_match_if_nil
+    assert @branch.matches?(@object, on: nil)
+  end
+
+  def test_should_ignore_to
+    assert @branch.matches?(@object, on: :park, to: :idling)
+  end
+
+  def test_should_ignore_from
+    assert @branch.matches?(@object, on: :park, from: :parked)
+  end
+
+  def test_should_not_be_included_in_known_states
+    assert_equal [], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_except_to_matcher_requirement_test.rb b/test/unit/branch/branch_with_except_to_matcher_requirement_test.rb
new file mode 100644
index 0000000..0dded7f
--- /dev/null
+++ b/test/unit/branch/branch_with_except_to_matcher_requirement_test.rb
@@ -0,0 +1,8 @@
+require_relative '../../test_helper'
+
+class BranchWithExceptToMatcherRequirementTest < StateMachinesTest
+  def test_should_raise_an_exception
+    exception = assert_raises(ArgumentError) { StateMachines::Branch.new(except_to: StateMachines::AllMatcher.instance) }
+    assert_equal ':except_to option cannot use matchers; use :to instead', exception.message
+  end
+end
diff --git a/test/unit/branch/branch_with_except_to_requirement_test.rb b/test/unit/branch/branch_with_except_to_requirement_test.rb
new file mode 100644
index 0000000..c47695e
--- /dev/null
+++ b/test/unit/branch/branch_with_except_to_requirement_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class BranchWithExceptToRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(except_to: :idling)
+  end
+
+  def test_should_use_a_blacklist_matcher
+    assert_instance_of StateMachines::BlacklistMatcher, @branch.state_requirements.first[:to]
+  end
+
+  def test_should_match_if_not_included
+    assert @branch.matches?(@object, to: :parked)
+  end
+
+  def test_should_not_match_if_included
+    refute @branch.matches?(@object, to: :idling)
+  end
+
+  def test_should_match_if_nil
+    assert @branch.matches?(@object, to: nil)
+  end
+
+  def test_should_ignore_from
+    assert @branch.matches?(@object, to: :parked, from: :idling)
+  end
+
+  def test_should_ignore_on
+    assert @branch.matches?(@object, to: :parked, on: :ignite)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:idling], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_from_matcher_requirement_test.rb b/test/unit/branch/branch_with_from_matcher_requirement_test.rb
new file mode 100644
index 0000000..bd9c784
--- /dev/null
+++ b/test/unit/branch/branch_with_from_matcher_requirement_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithFromMatcherRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(from: StateMachines::BlacklistMatcher.new([:idling, :parked]))
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, from: :first_gear)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, from: :idling)
+  end
+
+  def test_include_values_in_known_states
+    assert_equal [:idling, :parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_from_requirement_test.rb b/test/unit/branch/branch_with_from_requirement_test.rb
new file mode 100644
index 0000000..7ce97c3
--- /dev/null
+++ b/test/unit/branch/branch_with_from_requirement_test.rb
@@ -0,0 +1,45 @@
+require_relative '../../test_helper'
+
+class BranchWithFromRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(from: :parked)
+  end
+
+  def test_should_use_a_whitelist_matcher
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.state_requirements.first[:from]
+  end
+
+  def test_should_match_if_not_specified
+    assert @branch.matches?(@object, to: :idling)
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, from: :parked)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, from: :idling)
+  end
+
+  def test_should_not_match_if_nil
+    refute @branch.matches?(@object, from: nil)
+  end
+
+  def test_should_ignore_to
+    assert @branch.matches?(@object, from: :parked, to: :idling)
+  end
+
+  def test_should_ignore_on
+    assert @branch.matches?(@object, from: :parked, on: :ignite)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:parked], @branch.known_states
+  end
+
+  def test_should_include_requirement_in_match
+    match = @branch.match(@object, from: :parked)
+    assert_equal @branch.state_requirements.first[:from], match[:from]
+  end
+end
diff --git a/test/unit/branch/branch_with_if_conditional_test.rb b/test/unit/branch/branch_with_if_conditional_test.rb
new file mode 100644
index 0000000..2a1db37
--- /dev/null
+++ b/test/unit/branch/branch_with_if_conditional_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class BranchWithIfConditionalTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_have_an_if_condition
+    branch = StateMachines::Branch.new(if: lambda { true })
+    refute_nil branch.if_condition
+  end
+
+  def test_should_match_if_true
+    branch = StateMachines::Branch.new(if: lambda { true })
+    assert branch.matches?(@object)
+  end
+
+  def test_should_not_match_if_false
+    branch = StateMachines::Branch.new(if: lambda { false })
+    refute branch.matches?(@object)
+  end
+
+  def test_should_be_nil_if_unmatched
+    branch = StateMachines::Branch.new(if: lambda { false })
+    assert_nil branch.match(@object)
+  end
+end
diff --git a/test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb b/test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb
new file mode 100644
index 0000000..ff182af
--- /dev/null
+++ b/test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class BranchWithImplicitAndExplicitRequirementsTest < StateMachinesTest
+  def setup
+    @branch = StateMachines::Branch.new(parked: :idling, from: :parked)
+  end
+
+  def test_should_create_multiple_requirements
+    assert_equal 2, @branch.state_requirements.length
+  end
+
+  def test_should_create_implicit_requirements_for_implicit_options
+    assert(@branch.state_requirements.any? do |state_requirement|
+             state_requirement[:from].values == [:parked] && state_requirement[:to].values == [:idling]
+           end)
+  end
+
+  def test_should_create_implicit_requirements_for_explicit_options
+    assert(@branch.state_requirements.any? do |state_requirement|
+             state_requirement[:from].values == [:from] && state_requirement[:to].values == [:parked]
+           end)
+  end
+end
diff --git a/test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb b/test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb
new file mode 100644
index 0000000..1abf77d
--- /dev/null
+++ b/test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleFromRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(from: [:idling, :parked])
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, from: :idling)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, from: :first_gear)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:idling, :parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_implicit_requirement_test.rb b/test/unit/branch/branch_with_implicit_requirement_test.rb
new file mode 100644
index 0000000..daa1f57
--- /dev/null
+++ b/test/unit/branch/branch_with_implicit_requirement_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithImplicitRequirementTest < StateMachinesTest
+  def setup
+    @branch = StateMachines::Branch.new(parked: :idling, on: :ignite)
+  end
+
+  def test_should_create_an_event_requirement
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.event_requirement
+    assert_equal [:ignite], @branch.event_requirement.values
+  end
+
+  def test_should_use_a_whitelist_from_matcher
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.state_requirements.first[:from]
+  end
+
+  def test_should_use_a_whitelist_to_matcher
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.state_requirements.first[:to]
+  end
+end
diff --git a/test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb b/test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb
new file mode 100644
index 0000000..da2c2a8
--- /dev/null
+++ b/test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+
+class BranchWithImplicitToRequirementMatcherTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::BlacklistMatcher.new(:idling)
+    @branch = StateMachines::Branch.new(parked: @matcher)
+  end
+
+  def test_should_convert_from_to_whitelist_matcher
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.state_requirements.first[:from]
+  end
+
+  def test_should_not_convert_to_to_whitelist_matcher
+    assert_equal @matcher, @branch.state_requirements.first[:to]
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_except_from_requirements_test.rb b/test/unit/branch/branch_with_multiple_except_from_requirements_test.rb
new file mode 100644
index 0000000..72c8ef9
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_except_from_requirements_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleExceptFromRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(except_from: [:idling, :parked])
+  end
+
+  def test_should_match_if_not_included
+    assert @branch.matches?(@object, from: :first_gear)
+  end
+
+  def test_should_not_match_if_included
+    refute @branch.matches?(@object, from: :idling)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:idling, :parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_except_on_requirements_test.rb b/test/unit/branch/branch_with_multiple_except_on_requirements_test.rb
new file mode 100644
index 0000000..1d70262
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_except_on_requirements_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleExceptOnRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(except_on: [:ignite, :park])
+  end
+
+  def test_should_match_if_not_included
+    assert @branch.matches?(@object, on: :shift_up)
+  end
+
+  def test_should_not_match_if_included
+    refute @branch.matches?(@object, on: :ignite)
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_except_to_requirements_test.rb b/test/unit/branch/branch_with_multiple_except_to_requirements_test.rb
new file mode 100644
index 0000000..ce84a48
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_except_to_requirements_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleExceptToRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(except_to: [:idling, :parked])
+  end
+
+  def test_should_match_if_not_included
+    assert @branch.matches?(@object, to: :first_gear)
+  end
+
+  def test_should_not_match_if_included
+    refute @branch.matches?(@object, to: :idling)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:idling, :parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_from_requirements_test.rb b/test/unit/branch/branch_with_multiple_from_requirements_test.rb
new file mode 100644
index 0000000..e80f4ec
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_from_requirements_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+
+class BranchWithImplicitFromRequirementMatcherTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::BlacklistMatcher.new(:parked)
+    @branch = StateMachines::Branch.new(@matcher => :idling)
+  end
+
+  def test_should_not_convert_from_to_whitelist_matcher
+    assert_equal @matcher, @branch.state_requirements.first[:from]
+  end
+
+  def test_should_convert_to_to_whitelist_matcher
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.state_requirements.first[:to]
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_if_conditionals_test.rb b/test/unit/branch/branch_with_multiple_if_conditionals_test.rb
new file mode 100644
index 0000000..7a22001
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_if_conditionals_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleIfConditionalsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_match_if_all_are_true
+    branch = StateMachines::Branch.new(if: [lambda { true }, lambda { true }])
+    assert branch.match(@object)
+  end
+
+  def test_should_not_match_if_any_are_false
+    branch = StateMachines::Branch.new(if: [lambda { true }, lambda { false }])
+    refute branch.match(@object)
+
+    branch = StateMachines::Branch.new(if: [lambda { false }, lambda { true }])
+    refute branch.match(@object)
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_implicit_requirements_test.rb b/test/unit/branch/branch_with_multiple_implicit_requirements_test.rb
new file mode 100644
index 0000000..7d27342
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_implicit_requirements_test.rb
@@ -0,0 +1,53 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleImplicitRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(parked: :idling, idling: :first_gear, on: :ignite)
+  end
+
+  def test_should_create_multiple_state_requirements
+    assert_equal 2, @branch.state_requirements.length
+  end
+
+  def test_should_not_match_event_as_state_requirement
+    refute @branch.matches?(@object, from: :on, to: :ignite)
+  end
+
+  def test_should_match_if_from_included_in_any
+    assert @branch.matches?(@object, from: :parked)
+    assert @branch.matches?(@object, from: :idling)
+  end
+
+  def test_should_not_match_if_from_not_included_in_any
+    refute @branch.matches?(@object, from: :first_gear)
+  end
+
+  def test_should_match_if_to_included_in_any
+    assert @branch.matches?(@object, to: :idling)
+    assert @branch.matches?(@object, to: :first_gear)
+  end
+
+  def test_should_not_match_if_to_not_included_in_any
+    refute @branch.matches?(@object, to: :parked)
+  end
+
+  def test_should_match_if_all_options_match
+    assert @branch.matches?(@object, from: :parked, to: :idling, on: :ignite)
+    assert @branch.matches?(@object, from: :idling, to: :first_gear, on: :ignite)
+  end
+
+  def test_should_not_match_if_any_options_do_not_match
+    refute @branch.matches?(@object, from: :parked, to: :idling, on: :park)
+    refute @branch.matches?(@object, from: :parked, to: :first_gear, on: :park)
+  end
+
+  def test_should_include_all_known_states
+    assert_equal [:first_gear, :idling, :parked], @branch.known_states.sort_by { |state| state.to_s }
+  end
+
+  def test_should_not_duplicate_known_statse
+    branch = StateMachines::Branch.new(parked: :idling, first_gear: :idling)
+    assert_equal [:first_gear, :idling, :parked], branch.known_states.sort_by { |state| state.to_s }
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_on_requirements_test.rb b/test/unit/branch/branch_with_multiple_on_requirements_test.rb
new file mode 100644
index 0000000..ce84a48
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_on_requirements_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleExceptToRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(except_to: [:idling, :parked])
+  end
+
+  def test_should_match_if_not_included
+    assert @branch.matches?(@object, to: :first_gear)
+  end
+
+  def test_should_not_match_if_included
+    refute @branch.matches?(@object, to: :idling)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:idling, :parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_to_requirements_test.rb b/test/unit/branch/branch_with_multiple_to_requirements_test.rb
new file mode 100644
index 0000000..7c33f5a
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_to_requirements_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleToRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(to: [:idling, :parked])
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, to: :idling)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, to: :first_gear)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:idling, :parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_multiple_unless_conditionals_test.rb b/test/unit/branch/branch_with_multiple_unless_conditionals_test.rb
new file mode 100644
index 0000000..7b4c640
--- /dev/null
+++ b/test/unit/branch/branch_with_multiple_unless_conditionals_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithMultipleUnlessConditionalsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_match_if_all_are_false
+    branch = StateMachines::Branch.new(unless: [lambda { false }, lambda { false }])
+    assert branch.match(@object)
+  end
+
+  def test_should_not_match_if_any_are_true
+    branch = StateMachines::Branch.new(unless: [lambda { true }, lambda { false }])
+    refute branch.match(@object)
+
+    branch = StateMachines::Branch.new(unless: [lambda { false }, lambda { true }])
+    refute branch.match(@object)
+  end
+end
diff --git a/test/unit/branch/branch_with_nil_requirements_test.rb b/test/unit/branch/branch_with_nil_requirements_test.rb
new file mode 100644
index 0000000..b627d9c
--- /dev/null
+++ b/test/unit/branch/branch_with_nil_requirements_test.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class BranchWithNilRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(from: nil, to: nil)
+  end
+
+  def test_should_match_empty_query
+    assert @branch.matches?(@object)
+  end
+
+  def test_should_match_if_all_requirements_match
+    assert @branch.matches?(@object, from: nil, to: nil)
+  end
+
+  def test_should_not_match_if_from_not_included
+    refute @branch.matches?(@object, from: :parked)
+  end
+
+  def test_should_not_match_if_to_not_included
+    refute @branch.matches?(@object, to: :idling)
+  end
+
+  def test_should_include_all_known_states
+    assert_equal [nil], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_no_requirements_test.rb b/test/unit/branch/branch_with_no_requirements_test.rb
new file mode 100644
index 0000000..e1f831b
--- /dev/null
+++ b/test/unit/branch/branch_with_no_requirements_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class BranchWithNoRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new
+  end
+
+  def test_should_use_all_matcher_for_event_requirement
+    assert_equal StateMachines::AllMatcher.instance, @branch.event_requirement
+  end
+
+  def test_should_use_all_matcher_for_from_state_requirement
+    assert_equal StateMachines::AllMatcher.instance, @branch.state_requirements.first[:from]
+  end
+
+  def test_should_use_all_matcher_for_to_state_requirement
+    assert_equal StateMachines::AllMatcher.instance, @branch.state_requirements.first[:to]
+  end
+
+  def test_should_match_empty_query
+    assert @branch.matches?(@object, {})
+  end
+
+  def test_should_match_non_empty_query
+    assert @branch.matches?(@object, to: :idling, from: :parked, on: :ignite)
+  end
+
+  def test_should_include_all_requirements_in_match
+    match = @branch.match(@object, {})
+
+    assert_equal @branch.state_requirements.first[:from], match[:from]
+    assert_equal @branch.state_requirements.first[:to], match[:to]
+    assert_equal @branch.event_requirement, match[:on]
+  end
+end
diff --git a/test/unit/branch/branch_with_on_matcher_requirement_test.rb b/test/unit/branch/branch_with_on_matcher_requirement_test.rb
new file mode 100644
index 0000000..b9f3de6
--- /dev/null
+++ b/test/unit/branch/branch_with_on_matcher_requirement_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+
+class BranchWithOnMatcherRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(on: StateMachines::BlacklistMatcher.new([:ignite, :park]))
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, on: :shift_up)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, on: :ignite)
+  end
+end
diff --git a/test/unit/branch/branch_with_on_requirement_test.rb b/test/unit/branch/branch_with_on_requirement_test.rb
new file mode 100644
index 0000000..3f96e14
--- /dev/null
+++ b/test/unit/branch/branch_with_on_requirement_test.rb
@@ -0,0 +1,45 @@
+require_relative '../../test_helper'
+
+class BranchWithOnRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(on: :ignite)
+  end
+
+  def test_should_use_a_whitelist_matcher
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.event_requirement
+  end
+
+  def test_should_match_if_not_specified
+    assert @branch.matches?(@object, from: :parked)
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, on: :ignite)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, on: :park)
+  end
+
+  def test_should_not_match_if_nil
+    refute @branch.matches?(@object, on: nil)
+  end
+
+  def test_should_ignore_to
+    assert @branch.matches?(@object, on: :ignite, to: :parked)
+  end
+
+  def test_should_ignore_from
+    assert @branch.matches?(@object, on: :ignite, from: :parked)
+  end
+
+  def test_should_not_be_included_in_known_states
+    assert_equal [], @branch.known_states
+  end
+
+  def test_should_include_requirement_in_match
+    match = @branch.match(@object, on: :ignite)
+    assert_equal @branch.event_requirement, match[:on]
+  end
+end
diff --git a/test/unit/branch/branch_with_to_matcher_requirement_test.rb b/test/unit/branch/branch_with_to_matcher_requirement_test.rb
new file mode 100644
index 0000000..5a4f42e
--- /dev/null
+++ b/test/unit/branch/branch_with_to_matcher_requirement_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class BranchWithToMatcherRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(to: StateMachines::BlacklistMatcher.new([:idling, :parked]))
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, to: :first_gear)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, to: :idling)
+  end
+
+  def test_include_values_in_known_states
+    assert_equal [:idling, :parked], @branch.known_states
+  end
+end
diff --git a/test/unit/branch/branch_with_to_requirement_test.rb b/test/unit/branch/branch_with_to_requirement_test.rb
new file mode 100644
index 0000000..68080e8
--- /dev/null
+++ b/test/unit/branch/branch_with_to_requirement_test.rb
@@ -0,0 +1,45 @@
+require_relative '../../test_helper'
+
+class BranchWithToRequirementTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @branch = StateMachines::Branch.new(to: :idling)
+  end
+
+  def test_should_use_a_whitelist_matcher
+    assert_instance_of StateMachines::WhitelistMatcher, @branch.state_requirements.first[:to]
+  end
+
+  def test_should_match_if_not_specified
+    assert @branch.matches?(@object, from: :parked)
+  end
+
+  def test_should_match_if_included
+    assert @branch.matches?(@object, to: :idling)
+  end
+
+  def test_should_not_match_if_not_included
+    refute @branch.matches?(@object, to: :parked)
+  end
+
+  def test_should_not_match_if_nil
+    refute @branch.matches?(@object, to: nil)
+  end
+
+  def test_should_ignore_from
+    assert @branch.matches?(@object, to: :idling, from: :parked)
+  end
+
+  def test_should_ignore_on
+    assert @branch.matches?(@object, to: :idling, on: :ignite)
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:idling], @branch.known_states
+  end
+
+  def test_should_include_requirement_in_match
+    match = @branch.match(@object, to: :idling)
+    assert_equal @branch.state_requirements.first[:to], match[:to]
+  end
+end
diff --git a/test/unit/branch/branch_with_unless_conditional_test.rb b/test/unit/branch/branch_with_unless_conditional_test.rb
new file mode 100644
index 0000000..e7342bd
--- /dev/null
+++ b/test/unit/branch/branch_with_unless_conditional_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class BranchWithUnlessConditionalTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_have_an_unless_condition
+    branch = StateMachines::Branch.new(unless: lambda { true })
+    refute_nil branch.unless_condition
+  end
+
+  def test_should_match_if_false
+    branch = StateMachines::Branch.new(unless: lambda { false })
+    assert branch.matches?(@object)
+  end
+
+  def test_should_not_match_if_true
+    branch = StateMachines::Branch.new(unless: lambda { true })
+    refute branch.matches?(@object)
+  end
+
+  def test_should_be_nil_if_unmatched
+    branch = StateMachines::Branch.new(unless: lambda { true })
+    assert_nil branch.match(@object)
+  end
+end
diff --git a/test/unit/branch/branch_without_guards_test.rb b/test/unit/branch/branch_without_guards_test.rb
new file mode 100644
index 0000000..7bf64d7
--- /dev/null
+++ b/test/unit/branch/branch_without_guards_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class BranchWithoutGuardsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_match_if_if_is_false
+    branch = StateMachines::Branch.new(if: lambda { false })
+    assert branch.matches?(@object, guard: false)
+  end
+
+  def test_should_match_if_if_is_true
+    branch = StateMachines::Branch.new(if: lambda { true })
+    assert branch.matches?(@object, guard: false)
+  end
+
+  def test_should_match_if_unless_is_false
+    branch = StateMachines::Branch.new(unless: lambda { false })
+    assert branch.matches?(@object, guard: false)
+  end
+
+  def test_should_match_if_unless_is_true
+    branch = StateMachines::Branch.new(unless: lambda { true })
+    assert branch.matches?(@object, guard: false)
+  end
+end
diff --git a/test/unit/callback/callback_by_default_test.rb b/test/unit/callback/callback_by_default_test.rb
new file mode 100644
index 0000000..3547725
--- /dev/null
+++ b/test/unit/callback/callback_by_default_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class CallbackByDefaultTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before) {}
+  end
+
+  def test_should_have_type
+    assert_equal :before, @callback.type
+  end
+
+  def test_should_not_have_a_terminator
+    assert_nil @callback.terminator
+  end
+
+  def test_should_have_a_branch_with_all_matcher_requirements
+    assert_equal StateMachines::AllMatcher.instance, @callback.branch.event_requirement
+    assert_equal StateMachines::AllMatcher.instance, @callback.branch.state_requirements.first[:from]
+    assert_equal StateMachines::AllMatcher.instance, @callback.branch.state_requirements.first[:to]
+  end
+
+  def test_should_not_have_any_known_states
+    assert_equal [], @callback.known_states
+  end
+end
diff --git a/test/unit/callback/callback_test.rb b/test/unit/callback/callback_test.rb
new file mode 100644
index 0000000..c990a4d
--- /dev/null
+++ b/test/unit/callback/callback_test.rb
@@ -0,0 +1,53 @@
+require_relative '../../test_helper'
+
+class CallbackTest < StateMachinesTest
+  def test_should_raise_exception_if_invalid_type_specified
+    exception = assert_raises(ArgumentError) { StateMachines::Callback.new(:invalid) {} }
+    assert_equal 'Type must be :before, :after, :around, or :failure', exception.message
+  end
+
+  def test_should_not_raise_exception_if_using_before_type
+    StateMachines::Callback.new(:before) {}
+  end
+
+  def test_should_not_raise_exception_if_using_after_type
+    StateMachines::Callback.new(:after) {}
+  end
+
+  def test_should_not_raise_exception_if_using_around_type
+    StateMachines::Callback.new(:around) {}
+  end
+
+  def test_should_not_raise_exception_if_using_failure_type
+    StateMachines::Callback.new(:failure) {}
+  end
+
+  def test_should_raise_exception_if_no_methods_specified
+    exception = assert_raises(ArgumentError) { StateMachines::Callback.new(:before) }
+    assert_equal 'Method(s) for callback must be specified', exception.message
+  end
+
+  def test_should_not_raise_exception_if_method_specified_in_do_option
+    StateMachines::Callback.new(:before, do: :run)
+  end
+
+  def test_should_not_raise_exception_if_method_specified_as_argument
+    StateMachines::Callback.new(:before, :run)
+  end
+
+  def test_should_not_raise_exception_if_method_specified_as_block
+    StateMachines::Callback.new(:before, :run) {}
+  end
+
+  def test_should_not_raise_exception_if_implicit_option_specified
+    StateMachines::Callback.new(:before, do: :run, invalid: :valid)
+  end
+
+  def test_should_not_bind_to_objects
+    refute StateMachines::Callback.bind_to_object
+  end
+
+  def test_should_not_have_a_terminator
+    assert_nil StateMachines::Callback.terminator
+  end
+end
diff --git a/test/unit/callback/callback_with_application_bound_object_test.rb b/test/unit/callback/callback_with_application_bound_object_test.rb
new file mode 100644
index 0000000..f9e547a
--- /dev/null
+++ b/test/unit/callback/callback_with_application_bound_object_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class CallbackWithApplicationBoundObjectTest < StateMachinesTest
+  def setup
+    @original_bind_to_object = StateMachines::Callback.bind_to_object
+    StateMachines::Callback.bind_to_object = true
+
+    context = nil
+    @callback = StateMachines::Callback.new(:before, do: lambda { |*_args| context = self })
+
+    @object = Object.new
+    @callback.call(@object)
+    @context = context
+  end
+
+  def test_should_call_method_within_the_context_of_the_object
+    assert_equal @object, @context
+  end
+
+  def teardown
+    StateMachines::Callback.bind_to_object = @original_bind_to_object
+  end
+end
diff --git a/test/unit/callback/callback_with_application_terminator_test.rb b/test/unit/callback/callback_with_application_terminator_test.rb
new file mode 100644
index 0000000..47fa1ef
--- /dev/null
+++ b/test/unit/callback/callback_with_application_terminator_test.rb
@@ -0,0 +1,24 @@
+require_relative '../../test_helper'
+
+class CallbackWithApplicationTerminatorTest < StateMachinesTest
+  def setup
+    @original_terminator = StateMachines::Callback.terminator
+    StateMachines::Callback.terminator = lambda { |result| result == false }
+
+    @object = Object.new
+  end
+
+  def test_should_not_halt_if_terminator_does_not_match
+    callback = StateMachines::Callback.new(:before, do: lambda { true })
+    callback.call(@object)
+  end
+
+  def test_should_halt_if_terminator_matches
+    callback = StateMachines::Callback.new(:before, do: lambda { false })
+    assert_throws(:halt) { callback.call(@object) }
+  end
+
+  def teardown
+    StateMachines::Callback.terminator = @original_terminator
+  end
+end
diff --git a/test/unit/callback/callback_with_arguments_test.rb b/test/unit/callback/callback_with_arguments_test.rb
new file mode 100644
index 0000000..8393844
--- /dev/null
+++ b/test/unit/callback/callback_with_arguments_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class CallbackWithArgumentsTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, do: lambda { |*args| @args = args })
+
+    @object = Object.new
+    @callback.call(@object, {}, 1, 2, 3)
+  end
+
+  def test_should_call_method_with_all_arguments
+    assert_equal [@object, 1, 2, 3], @args
+  end
+end
diff --git a/test/unit/callback/callback_with_around_type_and_arguments_test.rb b/test/unit/callback/callback_with_around_type_and_arguments_test.rb
new file mode 100644
index 0000000..395876d
--- /dev/null
+++ b/test/unit/callback/callback_with_around_type_and_arguments_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class CallbackWithAroundTypeAndArgumentsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_include_object_if_specified
+    callback = StateMachines::Callback.new(:around, lambda { |object, block| @args = [object]; block.call })
+    callback.call(@object)
+    assert_equal [@object], @args
+  end
+
+  def test_should_include_arguments_if_specified
+    callback = StateMachines::Callback.new(:around, lambda { |object, arg1, arg2, arg3, block| @args = [object, arg1, arg2, arg3]; block.call })
+    callback.call(@object, {}, 1, 2, 3)
+    assert_equal [@object, 1, 2, 3], @args
+  end
+
+  def test_should_include_arguments_if_splat_used
+    callback = StateMachines::Callback.new(:around, lambda { |*args| block = args.pop; @args = args; block.call })
+    callback.call(@object, {}, 1, 2, 3)
+    assert_equal [@object, 1, 2, 3], @args
+  end
+end
diff --git a/test/unit/callback/callback_with_around_type_and_block_test.rb b/test/unit/callback/callback_with_around_type_and_block_test.rb
new file mode 100644
index 0000000..820cc28
--- /dev/null
+++ b/test/unit/callback/callback_with_around_type_and_block_test.rb
@@ -0,0 +1,44 @@
+require_relative '../../test_helper'
+
+class CallbackWithAroundTypeAndBlockTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @callbacks = []
+  end
+
+  def test_should_evaluate_before_without_after
+    callback = StateMachines::Callback.new(:around, lambda { |*args| block = args.pop; @args = args; block.call })
+    assert callback.call(@object)
+    assert_equal [@object], @args
+  end
+
+  def test_should_evaluate_after_without_before
+    callback = StateMachines::Callback.new(:around, lambda { |*args| block = args.pop; block.call; @args = args })
+    assert callback.call(@object)
+    assert_equal [@object], @args
+  end
+
+  def test_should_halt_if_not_yielded
+    callback = StateMachines::Callback.new(:around, lambda { |_block| })
+    assert_throws(:halt) { callback.call(@object) }
+  end
+
+  def test_should_call_block_after_before
+    callback = StateMachines::Callback.new(:around, lambda { |block| @callbacks << :before; block.call })
+    assert callback.call(@object) { @callbacks << :block }
+    assert_equal [:before, :block], @callbacks
+  end
+
+  def test_should_call_block_before_after
+    @callbacks = []
+    callback = StateMachines::Callback.new(:around, lambda { |block| block.call; @callbacks << :after })
+    assert callback.call(@object) { @callbacks << :block }
+    assert_equal [:block, :after], @callbacks
+  end
+
+  def test_should_halt_if_block_halts
+    callback = StateMachines::Callback.new(:around, lambda { |block| block.call; @callbacks << :after })
+    assert_throws(:halt) { callback.call(@object) { throw :halt }  }
+    assert_equal [], @callbacks
+  end
+end
diff --git a/test/unit/callback/callback_with_around_type_and_bound_method_test.rb b/test/unit/callback/callback_with_around_type_and_bound_method_test.rb
new file mode 100644
index 0000000..3633aad
--- /dev/null
+++ b/test/unit/callback/callback_with_around_type_and_bound_method_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class CallbackWithAroundTypeAndBoundMethodTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_call_method_within_the_context_of_the_object
+    context = nil
+    callback = StateMachines::Callback.new(:around, do: lambda { |block| context = self; block.call }, bind_to_object: true)
+    callback.call(@object, {}, 1, 2, 3)
+
+    assert_equal @object, context
+  end
+
+  def test_should_include_arguments_if_specified
+    context = nil
+    callback = StateMachines::Callback.new(:around, do: lambda { |*args| block = args.pop; context = args; block.call }, bind_to_object: true)
+    callback.call(@object, {}, 1, 2, 3)
+
+    assert_equal [1, 2, 3], context
+  end
+end
diff --git a/test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb b/test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb
new file mode 100644
index 0000000..2904f27
--- /dev/null
+++ b/test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb
@@ -0,0 +1,93 @@
+require_relative '../../test_helper'
+
+class CallbackWithAroundTypeAndMultipleMethodsTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:around, :run_1, :run_2)
+
+    class << @object = Object.new
+      attr_accessor :before_callbacks
+      attr_accessor :after_callbacks
+
+      def run_1
+        (@before_callbacks ||= []) << :run_1
+        yield
+        (@after_callbacks ||= []) << :run_1
+      end
+
+      def run_2
+        (@before_callbacks ||= []) << :run_2
+        yield
+        (@after_callbacks ||= []) << :run_2
+      end
+    end
+  end
+
+  def test_should_succeed
+    assert @callback.call(@object)
+  end
+
+  def test_should_evaluate_before_callbacks_in_order
+    @callback.call(@object)
+    assert_equal [:run_1, :run_2], @object.before_callbacks
+  end
+
+  def test_should_evaluate_after_callbacks_in_reverse_order
+    @callback.call(@object)
+    assert_equal [:run_2, :run_1], @object.after_callbacks
+  end
+
+  def test_should_call_block_after_before_callbacks
+    @callback.call(@object) { (@object.before_callbacks ||= []) << :block }
+    assert_equal [:run_1, :run_2, :block], @object.before_callbacks
+  end
+
+  def test_should_call_block_before_after_callbacks
+    @callback.call(@object) { (@object.after_callbacks ||= []) << :block }
+    assert_equal [:block, :run_2, :run_1], @object.after_callbacks
+  end
+
+  def test_should_halt_if_first_doesnt_yield
+    class << @object
+      remove_method :run_1
+      def run_1
+        (@before_callbacks ||= []) << :run_1
+      end
+    end
+
+    catch(:halt) do
+      @callback.call(@object) { (@object.before_callbacks ||= []) << :block }
+    end
+
+    assert_equal [:run_1], @object.before_callbacks
+    assert_nil @object.after_callbacks
+  end
+
+  def test_should_halt_if_last_doesnt_yield
+    class << @object
+      remove_method :run_2
+      def run_2
+        (@before_callbacks ||= []) << :run_2
+      end
+    end
+
+    catch(:halt) { @callback.call(@object) }
+    assert_equal [:run_1, :run_2], @object.before_callbacks
+    assert_nil @object.after_callbacks
+  end
+
+  def test_should_not_evaluate_further_methods_if_after_halts
+    class << @object
+      remove_method :run_2
+      def run_2
+        (@before_callbacks ||= []) << :run_2
+        yield
+        (@after_callbacks ||= []) << :run_2
+        throw :halt
+      end
+    end
+
+    catch(:halt) { @callback.call(@object) }
+    assert_equal [:run_1, :run_2], @object.before_callbacks
+    assert_equal [:run_2], @object.after_callbacks
+  end
+end
diff --git a/test/unit/callback/callback_with_around_type_and_terminator_test.rb b/test/unit/callback/callback_with_around_type_and_terminator_test.rb
new file mode 100644
index 0000000..c2c1c80
--- /dev/null
+++ b/test/unit/callback/callback_with_around_type_and_terminator_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class CallbackWithAroundTypeAndTerminatorTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_not_halt_if_terminator_does_not_match
+    callback = StateMachines::Callback.new(:around, do: lambda { |block| block.call(false); false }, terminator: lambda { |result| result == true })
+    callback.call(@object)
+  end
+
+  def test_should_not_halt_if_terminator_matches
+    callback = StateMachines::Callback.new(:around, do: lambda { |block| block.call(false); false }, terminator: lambda { |result| result == false })
+    callback.call(@object)
+  end
+end
diff --git a/test/unit/callback/callback_with_block_test.rb b/test/unit/callback/callback_with_block_test.rb
new file mode 100644
index 0000000..ad94cd2
--- /dev/null
+++ b/test/unit/callback/callback_with_block_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class CallbackWithBlockTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before) do |*args|
+      @args = args
+    end
+
+    @object = Object.new
+    @result = @callback.call(@object)
+  end
+
+  def test_should_be_successful
+    assert @result
+  end
+
+  def test_should_call_with_empty_context
+    assert_equal [@object], @args
+  end
+end
diff --git a/test/unit/callback/callback_with_bound_method_and_arguments_test.rb b/test/unit/callback/callback_with_bound_method_and_arguments_test.rb
new file mode 100644
index 0000000..3a3e4d9
--- /dev/null
+++ b/test/unit/callback/callback_with_bound_method_and_arguments_test.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class CallbackWithBoundMethodAndArgumentsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_include_single_argument_if_specified
+    context = nil
+    callback = StateMachines::Callback.new(:before, do: lambda { |arg1| context = [arg1] }, bind_to_object: true)
+    callback.call(@object, {}, 1)
+    assert_equal [1], context
+  end
+
+  def test_should_include_multiple_arguments_if_specified
+    context = nil
+    callback = StateMachines::Callback.new(:before, do: lambda { |arg1, arg2, arg3| context = [arg1, arg2, arg3] }, bind_to_object: true)
+    callback.call(@object, {}, 1, 2, 3)
+    assert_equal [1, 2, 3], context
+  end
+
+  def test_should_include_arguments_if_splat_used
+    context = nil
+    callback = StateMachines::Callback.new(:before, do: lambda { |*args| context = args }, bind_to_object: true)
+    callback.call(@object, {}, 1, 2, 3)
+    assert_equal [1, 2, 3], context
+  end
+end
diff --git a/test/unit/callback/callback_with_bound_method_test.rb b/test/unit/callback/callback_with_bound_method_test.rb
new file mode 100644
index 0000000..622b437
--- /dev/null
+++ b/test/unit/callback/callback_with_bound_method_test.rb
@@ -0,0 +1,35 @@
+require_relative '../../test_helper'
+
+class CallbackWithBoundMethodTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_call_method_within_the_context_of_the_object_for_block_methods
+    context = nil
+    callback = StateMachines::Callback.new(:before, do: lambda { |*args| context = [self] + args }, bind_to_object: true)
+    callback.call(@object, {}, 1, 2, 3)
+
+    assert_equal [@object, 1, 2, 3], context
+  end
+
+  def test_should_ignore_option_for_symbolic_methods
+    class << @object
+      attr_reader :context
+
+      def after_ignite(*args)
+        @context = args
+      end
+    end
+
+    callback = StateMachines::Callback.new(:before, do: :after_ignite, bind_to_object: true)
+    callback.call(@object)
+
+    assert_equal [], @object.context
+  end
+
+  def test_should_ignore_option_for_string_methods
+    callback = StateMachines::Callback.new(:before, do: '[1, 2, 3]', bind_to_object: true)
+    assert callback.call(@object)
+  end
+end
diff --git a/test/unit/callback/callback_with_do_method_test.rb b/test/unit/callback/callback_with_do_method_test.rb
new file mode 100644
index 0000000..0666151
--- /dev/null
+++ b/test/unit/callback/callback_with_do_method_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class CallbackWithDoMethodTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, do: lambda { |*args| @args = args })
+
+    @object = Object.new
+    @result = @callback.call(@object)
+  end
+
+  def test_should_be_successful
+    assert @result
+  end
+
+  def test_should_call_with_empty_context
+    assert_equal [@object], @args
+  end
+end
diff --git a/test/unit/callback/callback_with_explicit_requirements_test.rb b/test/unit/callback/callback_with_explicit_requirements_test.rb
new file mode 100644
index 0000000..84cb032
--- /dev/null
+++ b/test/unit/callback/callback_with_explicit_requirements_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class CallbackWithExplicitRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @callback = StateMachines::Callback.new(:before, from: :parked, to: :idling, on: :ignite, do: lambda {})
+  end
+
+  def test_should_call_with_empty_context
+    assert @callback.call(@object, {})
+  end
+
+  def test_should_not_call_if_from_not_included
+    refute @callback.call(@object, from: :idling)
+  end
+
+  def test_should_not_call_if_to_not_included
+    refute @callback.call(@object, to: :parked)
+  end
+
+  def test_should_not_call_if_on_not_included
+    refute @callback.call(@object, on: :park)
+  end
+
+  def test_should_call_if_all_requirements_met
+    assert @callback.call(@object, from: :parked, to: :idling, on: :ignite)
+  end
+
+  def test_should_include_in_known_states
+    assert_equal [:parked, :idling], @callback.known_states
+  end
+end
diff --git a/test/unit/callback/callback_with_if_condition_test.rb b/test/unit/callback/callback_with_if_condition_test.rb
new file mode 100644
index 0000000..36b396d
--- /dev/null
+++ b/test/unit/callback/callback_with_if_condition_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class CallbackWithIfConditionTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_call_if_true
+    callback = StateMachines::Callback.new(:before, if: lambda { true }, do: lambda {})
+    assert callback.call(@object)
+  end
+
+  def test_should_not_call_if_false
+    callback = StateMachines::Callback.new(:before, if: lambda { false }, do: lambda {})
+    refute callback.call(@object)
+  end
+end
diff --git a/test/unit/callback/callback_with_implicit_requirements_test.rb b/test/unit/callback/callback_with_implicit_requirements_test.rb
new file mode 100644
index 0000000..e8d2718
--- /dev/null
+++ b/test/unit/callback/callback_with_implicit_requirements_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class CallbackWithImplicitRequirementsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @callback = StateMachines::Callback.new(:before, parked: :idling, on: :ignite, do: lambda {})
+  end
+
+  def test_should_call_with_empty_context
+    assert @callback.call(@object, {})
+  end
+
+  def test_should_not_call_if_from_not_included
+    refute @callback.call(@object, from: :idling)
+  end
+
+  def test_should_not_call_if_to_not_included
+    refute @callback.call(@object, to: :parked)
+  end
+
+  def test_should_not_call_if_on_not_included
+    refute @callback.call(@object, on: :park)
+  end
+
+  def test_should_call_if_all_requirements_met
+    assert @callback.call(@object, from: :parked, to: :idling, on: :ignite)
+  end
+
+  def test_should_include_in_known_states
+    assert_equal [:parked, :idling], @callback.known_states
+  end
+end
diff --git a/test/unit/callback/callback_with_method_argument_test.rb b/test/unit/callback/callback_with_method_argument_test.rb
new file mode 100644
index 0000000..14f889b
--- /dev/null
+++ b/test/unit/callback/callback_with_method_argument_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class CallbackWithMethodArgumentTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, lambda { |*args| @args = args })
+
+    @object = Object.new
+    @result = @callback.call(@object)
+  end
+
+  def test_should_be_successful
+    assert @result
+  end
+
+  def test_should_call_with_empty_context
+    assert_equal [@object], @args
+  end
+end
diff --git a/test/unit/callback/callback_with_mixed_methods_test.rb b/test/unit/callback/callback_with_mixed_methods_test.rb
new file mode 100644
index 0000000..f51f68a
--- /dev/null
+++ b/test/unit/callback/callback_with_mixed_methods_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class CallbackWithMixedMethodsTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, :run_argument, do: :run_do) do |object|
+      object.callbacks << :block
+    end
+
+    class << @object = Object.new
+      attr_accessor :callbacks
+
+      def run_argument
+        (@callbacks ||= []) << :argument
+      end
+
+      def run_do
+        (@callbacks ||= []) << :do
+      end
+    end
+
+    @result = @callback.call(@object)
+  end
+
+  def test_should_be_successful
+    assert @result
+  end
+
+  def test_should_call_each_callback_in_order
+    assert_equal [:argument, :do, :block], @object.callbacks
+  end
+end
diff --git a/test/unit/callback/callback_with_multiple_bound_methods_test.rb b/test/unit/callback/callback_with_multiple_bound_methods_test.rb
new file mode 100644
index 0000000..41bc9da
--- /dev/null
+++ b/test/unit/callback/callback_with_multiple_bound_methods_test.rb
@@ -0,0 +1,21 @@
+require_relative '../../test_helper'
+
+class CallbackWithMultipleBoundMethodsTest < StateMachinesTest
+  def setup
+    @object = Object.new
+
+    first_context = nil
+    second_context = nil
+
+    @callback = StateMachines::Callback.new(:before, do: [lambda { first_context = self }, lambda { second_context = self }], bind_to_object: true)
+    @callback.call(@object)
+
+    @first_context = first_context
+    @second_context = second_context
+  end
+
+  def test_should_call_each_method_within_the_context_of_the_object
+    assert_equal @object, @first_context
+    assert_equal @object, @second_context
+  end
+end
diff --git a/test/unit/callback/callback_with_multiple_do_methods_test.rb b/test/unit/callback/callback_with_multiple_do_methods_test.rb
new file mode 100644
index 0000000..2b088f1
--- /dev/null
+++ b/test/unit/callback/callback_with_multiple_do_methods_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class CallbackWithMultipleDoMethodsTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, do: [:run_1, :run_2])
+
+    class << @object = Object.new
+      attr_accessor :callbacks
+
+      def run_1
+        (@callbacks ||= []) << :run_1
+      end
+
+      def run_2
+        (@callbacks ||= []) << :run_2
+      end
+    end
+
+    @result = @callback.call(@object)
+  end
+
+  def test_should_be_successful
+    assert @result
+  end
+
+  def test_should_call_each_callback_in_order
+    assert_equal [:run_1, :run_2], @object.callbacks
+  end
+end
diff --git a/test/unit/callback/callback_with_multiple_method_arguments_test.rb b/test/unit/callback/callback_with_multiple_method_arguments_test.rb
new file mode 100644
index 0000000..5724012
--- /dev/null
+++ b/test/unit/callback/callback_with_multiple_method_arguments_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class CallbackWithMultipleMethodArgumentsTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, :run_1, :run_2)
+
+    class << @object = Object.new
+      attr_accessor :callbacks
+
+      def run_1
+        (@callbacks ||= []) << :run_1
+      end
+
+      def run_2
+        (@callbacks ||= []) << :run_2
+      end
+    end
+
+    @result = @callback.call(@object)
+  end
+
+  def test_should_be_successful
+    assert @result
+  end
+
+  def test_should_call_each_callback_in_order
+    assert_equal [:run_1, :run_2], @object.callbacks
+  end
+end
diff --git a/test/unit/callback/callback_with_terminator_test.rb b/test/unit/callback/callback_with_terminator_test.rb
new file mode 100644
index 0000000..c513a52
--- /dev/null
+++ b/test/unit/callback/callback_with_terminator_test.rb
@@ -0,0 +1,22 @@
+require_relative '../../test_helper'
+
+class CallbackWithTerminatorTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_not_halt_if_terminator_does_not_match
+    callback = StateMachines::Callback.new(:before, do: lambda { false }, terminator: lambda { |result| result == true })
+    callback.call(@object)
+  end
+
+  def test_should_halt_if_terminator_matches
+    callback = StateMachines::Callback.new(:before, do: lambda { false }, terminator: lambda { |result| result == false })
+    assert_throws(:halt) { callback.call(@object) }
+  end
+
+  def test_should_halt_if_terminator_matches_any_method
+    callback = StateMachines::Callback.new(:before, do: [lambda { true }, lambda { false }], terminator: lambda { |result| result == false })
+    assert_throws(:halt) { callback.call(@object) }
+  end
+end
diff --git a/test/unit/callback/callback_with_unbound_method_test.rb b/test/unit/callback/callback_with_unbound_method_test.rb
new file mode 100644
index 0000000..3127399
--- /dev/null
+++ b/test/unit/callback/callback_with_unbound_method_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class CallbackWithUnboundMethodTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, do: lambda { |*args| @context = args.unshift(self) })
+
+    @object = Object.new
+    @callback.call(@object, {}, 1, 2, 3)
+  end
+
+  def test_should_call_method_outside_the_context_of_the_object
+    assert_equal [self, @object, 1, 2, 3], @context
+  end
+end
diff --git a/test/unit/callback/callback_with_unless_condition_test.rb b/test/unit/callback/callback_with_unless_condition_test.rb
new file mode 100644
index 0000000..b13fba1
--- /dev/null
+++ b/test/unit/callback/callback_with_unless_condition_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class CallbackWithUnlessConditionTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_call_if_false
+    callback = StateMachines::Callback.new(:before, unless: lambda { false }, do: lambda {})
+    assert callback.call(@object)
+  end
+
+  def test_should_not_call_if_true
+    callback = StateMachines::Callback.new(:before, unless: lambda { true }, do: lambda {})
+    refute callback.call(@object)
+  end
+end
diff --git a/test/unit/callback/callback_without_arguments_test.rb b/test/unit/callback/callback_without_arguments_test.rb
new file mode 100644
index 0000000..992a211
--- /dev/null
+++ b/test/unit/callback/callback_without_arguments_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class CallbackWithoutArgumentsTest < StateMachinesTest
+  def setup
+    @callback = StateMachines::Callback.new(:before, do: lambda { |object| @arg = object })
+
+    @object = Object.new
+    @callback.call(@object, {}, 1, 2, 3)
+  end
+
+  def test_should_call_method_with_object_as_argument
+    assert_equal @object, @arg
+  end
+end
diff --git a/test/unit/callback/callback_without_terminator_test.rb b/test/unit/callback/callback_without_terminator_test.rb
new file mode 100644
index 0000000..5878700
--- /dev/null
+++ b/test/unit/callback/callback_without_terminator_test.rb
@@ -0,0 +1,12 @@
+require_relative '../../test_helper'
+
+class CallbackWithoutTerminatorTest < StateMachinesTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_not_halt_if_result_is_false
+    callback = StateMachines::Callback.new(:before, do: lambda { false }, terminator: nil)
+    callback.call(@object)
+  end
+end
diff --git a/test/unit/error/error_by_default_test.rb b/test/unit/error/error_by_default_test.rb
new file mode 100644
index 0000000..e725bc8
--- /dev/null
+++ b/test/unit/error/error_by_default_test.rb
@@ -0,0 +1,21 @@
+require_relative '../../test_helper'
+
+class ErrorByDefaultTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(@machine)
+  end
+
+  def test_should_not_have_any_nodes
+    assert_equal 0, @collection.length
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @collection.machine
+  end
+
+  def test_should_index_by_name
+    @collection << object = Struct.new(:name).new(:parked)
+    assert_equal object, @collection[:parked]
+  end
+end
diff --git a/test/unit/error/error_with_message_test.rb b/test/unit/error/error_with_message_test.rb
new file mode 100644
index 0000000..a5c4ea0
--- /dev/null
+++ b/test/unit/error/error_with_message_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class ErrorWithMessageTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(@machine)
+  end
+
+  def test_should_raise_exception_if_invalid_option_specified
+    exception = assert_raises(ArgumentError) { StateMachines::NodeCollection.new(@machine, invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :index', exception.message
+  end
+
+  def test_should_raise_exception_on_lookup_if_invalid_index_specified
+    exception = assert_raises(ArgumentError) { @collection[:something, :invalid] }
+    assert_equal 'Invalid index: :invalid', exception.message
+  end
+
+  def test_should_raise_exception_on_fetch_if_invalid_index_specified
+    exception = assert_raises(ArgumentError) { @collection.fetch(:something, :invalid) }
+    assert_equal 'Invalid index: :invalid', exception.message
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_base_test.rb b/test/unit/eval_helper/eval_helpers_base_test.rb
new file mode 100644
index 0000000..d7cbbbf
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_base_test.rb
@@ -0,0 +1,8 @@
+require_relative '../../test_helper'
+
+class EvalHelpersBaseTest < StateMachinesTest
+  include StateMachines::EvalHelpers
+
+  def default_test
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb b/test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb
new file mode 100644
index 0000000..bfd8c1b
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcBlockAndExplicitArgumentsTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = lambda { |object, arg1, arg2, arg3, block| [object, arg1, arg2, arg3, block] }
+  end
+
+  def test_should_call_method_on_object_with_all_arguments_and_block
+    block = lambda { true }
+    assert_equal [@object, 1, 2, 3, block], evaluate_method(@object, @proc, 1, 2, 3, &block)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb b/test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb
new file mode 100644
index 0000000..3e520cc
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcBlockAndImplicitArgumentsTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = lambda { |*args| args }
+  end
+
+  def test_should_call_method_on_object_with_all_arguments_and_block
+    block = lambda { true }
+    assert_equal [@object, 1, 2, 3, block], evaluate_method(@object, @proc, 1, 2, 3, &block)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_proc_test.rb b/test/unit/eval_helper/eval_helpers_proc_test.rb
new file mode 100644
index 0000000..f6ded78
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = ->(obj) { obj }
+  end
+
+  def test_should_call_proc_with_object_as_argument
+    assert_equal @object, evaluate_method(@object, @proc, 1, 2, 3)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb b/test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb
new file mode 100644
index 0000000..30bdb87
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcWithArgumentsTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = lambda { |*args| args }
+  end
+
+  def test_should_call_method_with_all_arguments
+    assert_equal [@object, 1, 2, 3], evaluate_method(@object, @proc, 1, 2, 3)
+  end
+end
\ No newline at end of file
diff --git a/test/unit/eval_helper/eval_helpers_proc_with_block_test.rb b/test/unit/eval_helper/eval_helpers_proc_with_block_test.rb
new file mode 100644
index 0000000..a05eeb1
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_with_block_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcWithBlockTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = lambda { |_obj, block| block.call }
+  end
+
+  def test_should_call_method_on_object_with_block
+    assert_equal true, evaluate_method(@object, @proc, 1, 2, 3) { true }
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb b/test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb
new file mode 100644
index 0000000..8184182
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcWithoutArgumentsTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = lambda { |*args| args }
+    class << @proc
+      def arity
+        0
+      end
+    end
+  end
+
+  def test_should_call_proc_with_no_arguments
+    assert_equal [], evaluate_method(@object, @proc, 1, 2, 3)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb b/test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb
new file mode 100644
index 0000000..78d633e
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcWithBlockWithoutObjectTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = lambda { |block| [block] }
+  end
+
+  def test_should_call_proc_with_block_only
+    block = lambda { true }
+    assert_equal [block], evaluate_method(@object, @proc, 1, 2, 3, &block)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb b/test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb
new file mode 100644
index 0000000..0405dc9
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb
@@ -0,0 +1,19 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersProcWithBlockWithoutArgumentsTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+    @proc = lambda { |*args| args }
+    class << @proc
+      def arity
+        0
+      end
+    end
+  end
+
+  def test_should_call_proc_without_arguments
+    block = lambda { true }
+    assert_equal [], evaluate_method(@object, @proc, 1, 2, 3, &block)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_string_test.rb b/test/unit/eval_helper/eval_helpers_string_test.rb
new file mode 100644
index 0000000..0977c5f
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_string_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test.rb'
+
+class EvalHelpersStringTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_evaluate_string
+    assert_equal 1, evaluate_method(@object, '1')
+  end
+
+  def test_should_evaluate_string_within_object_context
+    @object.instance_variable_set('@value', 1)
+    assert_equal 1, evaluate_method(@object, '@value')
+  end
+
+  def test_should_ignore_additional_arguments
+    assert_equal 1, evaluate_method(@object, '1', 2, 3, 4)
+  end
+end
+
+
+
+
diff --git a/test/unit/eval_helper/eval_helpers_string_with_block_test.rb b/test/unit/eval_helper/eval_helpers_string_with_block_test.rb
new file mode 100644
index 0000000..1094bfd
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_string_with_block_test.rb
@@ -0,0 +1,12 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersStringWithBlockTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_call_method_on_object_with_block
+    assert_equal 1, evaluate_method(@object, 'yield') { 1 }
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb b/test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb
new file mode 100644
index 0000000..698010c
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolMethodMissingTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      def method_missing(symbol, *args)
+        send("method_missing_#{symbol}", *args)
+      end
+
+      def method_missing_callback(*args)
+        args
+      end
+    end
+  end
+
+  def test_should_call_dynamic_method_with_all_arguments
+    assert_equal [1, 2, 3], evaluate_method(@object, :callback, 1, 2, 3)
+  end
+end
\ No newline at end of file
diff --git a/test/unit/eval_helper/eval_helpers_symbol_private_test.rb b/test/unit/eval_helper/eval_helpers_symbol_private_test.rb
new file mode 100644
index 0000000..d24d5a2
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_private_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolPrivateTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      private
+      def callback
+        true
+      end
+    end
+  end
+
+  def test_should_call_method_on_object_with_no_arguments
+    assert_equal true, evaluate_method(@object, :callback, 1, 2, 3)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_symbol_protected_test.rb b/test/unit/eval_helper/eval_helpers_symbol_protected_test.rb
new file mode 100644
index 0000000..de2e96d
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_protected_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolProtectedTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      protected
+      def callback
+        true
+      end
+    end
+  end
+
+  def test_should_call_method_on_object_with_no_arguments
+    assert_equal true, evaluate_method(@object, :callback, 1, 2, 3)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb b/test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb
new file mode 100644
index 0000000..122e2e7
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolTaintedMethodTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      def callback
+        true
+      end
+
+      taint
+    end
+  end
+
+  def test_should_not_raise_security_error
+    evaluate_method(@object, :callback, 1, 2, 3)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_symbol_test.rb b/test/unit/eval_helper/eval_helpers_symbol_test.rb
new file mode 100644
index 0000000..426952a
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      def callback
+        true
+      end
+    end
+  end
+
+  def test_should_call_method_on_object_with_no_arguments
+    assert_equal true, evaluate_method(@object, :callback, 1, 2, 3)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb b/test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb
new file mode 100644
index 0000000..26f199c
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolWithArgumentsAndBlockTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      def callback(*args)
+        args << yield
+      end
+    end
+  end
+
+  def test_should_call_method_on_object_with_all_arguments_and_block
+    assert_equal [1, 2, 3, true], evaluate_method(@object, :callback, 1, 2, 3) { true }
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb b/test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb
new file mode 100644
index 0000000..bccdb8f
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolWithArgumentsTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      def callback(*args)
+        args
+      end
+    end
+  end
+
+  def test_should_call_method_with_all_arguments
+    assert_equal [1, 2, 3], evaluate_method(@object, :callback, 1, 2, 3)
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb b/test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb
new file mode 100644
index 0000000..ffd2f36
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersSymbolWithBlockTest < EvalHelpersBaseTest
+  def setup
+    class << (@object = Object.new)
+      def callback
+        yield
+      end
+    end
+  end
+
+  def test_should_call_method_on_object_with_block
+    assert_equal true, evaluate_method(@object, :callback) { true }
+  end
+end
diff --git a/test/unit/eval_helper/eval_helpers_test.rb b/test/unit/eval_helper/eval_helpers_test.rb
new file mode 100644
index 0000000..1b395b5
--- /dev/null
+++ b/test/unit/eval_helper/eval_helpers_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+require_relative '../../unit/eval_helper/eval_helpers_base_test'
+
+class EvalHelpersTest < EvalHelpersBaseTest
+  def setup
+    @object = Object.new
+  end
+
+  def test_should_raise_exception_if_method_is_not_symbol_string_or_proc
+    exception = assert_raises(ArgumentError) { evaluate_method(@object, 1) }
+    assert_match(/Methods must/, exception.message)
+  end
+end
diff --git a/test/unit/event/event_after_being_copied_test.rb b/test/unit/event/event_after_being_copied_test.rb
new file mode 100644
index 0000000..8ff8d90
--- /dev/null
+++ b/test/unit/event/event_after_being_copied_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class EventAfterBeingCopiedTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @copied_event = @event.dup
+  end
+
+  def test_should_not_have_the_same_collection_of_branches
+    refute_same @event.branches, @copied_event.branches
+  end
+
+  def test_should_not_have_the_same_collection_of_known_states
+    refute_same @event.known_states, @copied_event.known_states
+  end
+end
diff --git a/test/unit/event/event_by_default_test.rb b/test/unit/event/event_by_default_test.rb
new file mode 100644
index 0000000..e35c072
--- /dev/null
+++ b/test/unit/event/event_by_default_test.rb
@@ -0,0 +1,60 @@
+require_relative '../../test_helper'
+
+
+class EventByDefaultTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+
+    @object = @klass.new
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @event.machine
+  end
+
+  def test_should_have_a_name
+    assert_equal :ignite, @event.name
+  end
+
+  def test_should_have_a_qualified_name
+    assert_equal :ignite, @event.qualified_name
+  end
+
+  def test_should_have_a_human_name
+    assert_equal 'ignite', @event.human_name
+  end
+
+  def test_should_not_have_any_branches
+    assert @event.branches.empty?
+  end
+
+  def test_should_have_no_known_states
+    assert @event.known_states.empty?
+  end
+
+  def test_should_not_be_able_to_fire
+    refute @event.can_fire?(@object)
+  end
+
+  def test_should_not_have_a_transition
+    assert_nil @event.transition_for(@object)
+  end
+
+  def test_should_define_a_predicate
+    assert @object.respond_to?(:can_ignite?)
+  end
+
+  def test_should_define_a_transition_accessor
+    assert @object.respond_to?(:ignite_transition)
+  end
+
+  def test_should_define_an_action
+    assert @object.respond_to?(:ignite)
+  end
+
+  def test_should_define_a_bang_action
+    assert @object.respond_to?(:ignite!)
+  end
+end
diff --git a/test/unit/event/event_context_test.rb b/test/unit/event/event_context_test.rb
new file mode 100644
index 0000000..a57182c
--- /dev/null
+++ b/test/unit/event/event_context_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+
+class EventContextTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite, human_name: 'start')
+  end
+
+  def test_should_evaluate_within_the_event
+    scope = nil
+    @event.context { scope = self }
+    assert_equal @event, scope
+  end
+end
+
diff --git a/test/unit/event/event_on_failure_test.rb b/test/unit/event/event_on_failure_test.rb
new file mode 100644
index 0000000..5199419
--- /dev/null
+++ b/test/unit/event/event_on_failure_test.rb
@@ -0,0 +1,44 @@
+require_relative '../../test_helper'
+require_relative '../../files/integrations/event_on_failure_integration'
+
+class EventOnFailureTest < StateMachinesTest
+  def setup
+    StateMachines::Integrations.reset
+    StateMachines::Integrations.register(EventOnFailureIntegration)
+    @klass = Class.new do
+      attr_accessor :errors
+    end
+
+    @machine = StateMachines::Machine.new(@klass, integration: :event_on_failure_integration)
+    @machine.state :parked
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_invalidate_the_state
+    @event.fire(@object)
+    assert_equal ['cannot transition via "ignite"'], @object.errors
+  end
+
+  def test_should_run_failure_callbacks
+    callback_args = nil
+    @machine.after_failure { |*args| callback_args = args }
+
+    @event.fire(@object)
+
+    object, transition = callback_args
+    assert_equal @object, object
+    refute_nil transition
+    assert_equal @object, transition.object
+    assert_equal @machine, transition.machine
+    assert_equal :ignite, transition.event
+    assert_equal :parked, transition.from_name
+    assert_equal :parked, transition.to_name
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/event/event_test.rb b/test/unit/event/event_test.rb
new file mode 100644
index 0000000..1d42418
--- /dev/null
+++ b/test/unit/event/event_test.rb
@@ -0,0 +1,34 @@
+require_relative '../../test_helper'
+
+class EventTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition parked: :idling
+  end
+
+  def test_should_allow_changing_machine
+    new_machine = StateMachines::Machine.new(Class.new)
+    @event.machine = new_machine
+    assert_equal new_machine, @event.machine
+  end
+
+  def test_should_allow_changing_human_name
+    @event.human_name = 'Stop'
+    assert_equal 'Stop', @event.human_name
+  end
+
+  def test_should_provide_matcher_helpers_during_initialization
+    matchers = []
+
+    @event.instance_eval do
+      matchers = [all, any, same]
+    end
+
+    assert_equal [StateMachines::AllMatcher.instance, StateMachines::AllMatcher.instance, StateMachines::LoopbackMatcher.instance], matchers
+  end
+
+  def test_should_use_pretty_inspect
+    assert_match '#<StateMachines::Event name=:ignite transitions=[:parked => :idling]>', @event.inspect
+  end
+end
diff --git a/test/unit/event/event_transitions_test.rb b/test/unit/event/event_transitions_test.rb
new file mode 100644
index 0000000..e4d9aa3
--- /dev/null
+++ b/test/unit/event/event_transitions_test.rb
@@ -0,0 +1,62 @@
+require_relative '../../test_helper'
+
+class EventTransitionsTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+  end
+
+  def test_should_not_raise_exception_if_implicit_option_specified
+    @event.transition(invalid: :valid)
+  end
+
+  def test_should_not_allow_on_option
+    exception = assert_raises(ArgumentError) { @event.transition(on: :ignite) }
+    assert_equal 'Unknown key: :on. Valid keys are: :from, :to, :except_from, :except_to, :if, :unless', exception.message
+  end
+
+  def test_should_automatically_set_on_option
+    branch = @event.transition(to: :idling)
+    assert_instance_of StateMachines::WhitelistMatcher, branch.event_requirement
+    assert_equal [:ignite], branch.event_requirement.values
+  end
+
+  def test_should_not_allow_except_on_option
+    exception = assert_raises(ArgumentError) { @event.transition(except_on: :ignite) }
+    assert_equal 'Unknown key: :except_on. Valid keys are: :from, :to, :except_from, :except_to, :if, :unless', exception.message
+  end
+
+  def test_should_allow_transitioning_without_a_to_state
+    @event.transition(from: :parked)
+  end
+
+  def test_should_allow_transitioning_without_a_from_state
+    @event.transition(to: :idling)
+  end
+
+  def test_should_allow_except_from_option
+    @event.transition(except_from: :idling)
+  end
+
+  def test_should_allow_except_to_option
+    @event.transition(except_to: :idling)
+  end
+
+  def test_should_allow_transitioning_from_a_single_state
+    assert @event.transition(parked: :idling)
+  end
+
+  def test_should_allow_transitioning_from_multiple_states
+    assert @event.transition([:parked, :idling] => :idling)
+  end
+
+  def test_should_allow_transitions_to_multiple_states
+    assert @event.transition(parked: [:parked, :idling])
+  end
+
+  def test_should_have_transitions
+    branch = @event.transition(to: :idling)
+    assert_equal [branch], @event.branches
+  end
+end
+
diff --git a/test/unit/event/event_with_conflicting_helpers_after_definition_test.rb b/test/unit/event/event_with_conflicting_helpers_after_definition_test.rb
new file mode 100644
index 0000000..6e8c134
--- /dev/null
+++ b/test/unit/event/event_with_conflicting_helpers_after_definition_test.rb
@@ -0,0 +1,79 @@
+require_relative '../../test_helper'
+require 'stringio'
+
+class EventWithConflictingHelpersAfterDefinitionTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @klass = Class.new do
+      def can_ignite?
+        0
+      end
+
+      def ignite_transition
+        0
+      end
+
+      def ignite
+        0
+      end
+
+      def ignite!
+        0
+      end
+    end
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @object = @klass.new
+  end
+
+  def test_should_not_redefine_predicate
+    assert_equal 0, @object.can_ignite?
+  end
+
+  def test_should_not_redefine_transition_accessor
+    assert_equal 0, @object.ignite_transition
+  end
+
+  def test_should_not_redefine_action
+    assert_equal 0, @object.ignite
+  end
+
+  def test_should_not_redefine_bang_action
+    assert_equal 0, @object.ignite!
+  end
+
+  def test_should_allow_super_chaining
+    @klass.class_eval do
+      def can_ignite?
+        super
+      end
+
+      def ignite_transition
+        super
+      end
+
+      def ignite
+        super
+      end
+
+      def ignite!
+        super
+      end
+    end
+
+    assert_equal false, @object.can_ignite?
+    assert_equal nil, @object.ignite_transition
+    assert_equal false, @object.ignite
+    assert_raises(StateMachines::InvalidTransition) { @object.ignite! }
+  end
+
+  def test_should_not_output_warning
+    assert_equal '', $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
+
diff --git a/test/unit/event/event_with_conflicting_helpers_before_definition_test.rb b/test/unit/event/event_with_conflicting_helpers_before_definition_test.rb
new file mode 100644
index 0000000..f4a539a
--- /dev/null
+++ b/test/unit/event/event_with_conflicting_helpers_before_definition_test.rb
@@ -0,0 +1,58 @@
+require_relative '../../test_helper'
+require 'stringio'
+
+class EventWithConflictingHelpersBeforeDefinitionTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @superclass = Class.new do
+      def can_ignite?
+        0
+      end
+
+      def ignite_transition
+        0
+      end
+
+      def ignite
+        0
+      end
+
+      def ignite!
+        0
+      end
+    end
+    @klass = Class.new(@superclass)
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @object = @klass.new
+  end
+
+  def test_should_not_redefine_predicate
+    assert_equal 0, @object.can_ignite?
+  end
+
+  def test_should_not_redefine_transition_accessor
+    assert_equal 0, @object.ignite_transition
+  end
+
+  def test_should_not_redefine_action
+    assert_equal 0, @object.ignite
+  end
+
+  def test_should_not_redefine_bang_action
+    assert_equal 0, @object.ignite!
+  end
+
+  def test_should_output_warning
+    expected = %w(can_ignite? ignite_transition ignite ignite!).map do |method|
+      "Instance method \"#{method}\" is already defined in #{@superclass}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n"
+    end.join
+
+    assert_equal expected, $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
diff --git a/test/unit/event/event_with_conflicting_machine_test.rb b/test/unit/event/event_with_conflicting_machine_test.rb
new file mode 100644
index 0000000..2b85af8
--- /dev/null
+++ b/test/unit/event/event_with_conflicting_machine_test.rb
@@ -0,0 +1,48 @@
+require_relative '../../test_helper'
+require 'stringio'
+
+class EventWithConflictingMachineTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @klass = Class.new
+    @state_machine = StateMachines::Machine.new(@klass, :state)
+    @state_machine.state :parked, :idling
+    @state_machine.events << @state_event = StateMachines::Event.new(@state_machine, :ignite)
+  end
+
+  def test_should_not_overwrite_first_event
+    @status_machine = StateMachines::Machine.new(@klass, :status)
+    @status_machine.state :first_gear, :second_gear
+    @status_machine.events << @status_event = StateMachines::Event.new(@status_machine, :ignite)
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @object.status = 'first_gear'
+
+    @state_event.transition(parked: :idling)
+    @status_event.transition(parked: :first_gear)
+
+    @object.ignite
+    assert_equal 'idling', @object.state
+    assert_equal 'first_gear', @object.status
+  end
+
+  def test_should_output_warning
+    @status_machine = StateMachines::Machine.new(@klass, :status)
+    @status_machine.events << @status_event = StateMachines::Event.new(@status_machine, :ignite)
+
+    assert_equal "Event :ignite for :status is already defined in :state\n", $stderr.string
+  end
+
+  def test_should_not_output_warning_if_using_different_namespace
+    @status_machine = StateMachines::Machine.new(@klass, :status, namespace: 'alarm')
+    @status_machine.events << @status_event = StateMachines::Event.new(@status_machine, :ignite)
+
+    assert_equal '', $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
diff --git a/test/unit/event/event_with_dynamic_human_name_test.rb b/test/unit/event/event_with_dynamic_human_name_test.rb
new file mode 100644
index 0000000..11f05ea
--- /dev/null
+++ b/test/unit/event/event_with_dynamic_human_name_test.rb
@@ -0,0 +1,26 @@
+require_relative '../../test_helper'
+
+class EventWithDynamicHumanNameTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite, human_name: lambda { |_event, object| ['start', object] })
+  end
+
+  def test_should_use_custom_human_name
+    human_name, klass = @event.human_name
+    assert_equal 'start', human_name
+    assert_equal @klass, klass
+  end
+
+  def test_should_allow_custom_class_to_be_passed_through
+    human_name, klass = @event.human_name(1)
+    assert_equal 'start', human_name
+    assert_equal 1, klass
+  end
+
+  def test_should_not_cache_value
+    refute_same @event.human_name, @event.human_name
+  end
+end
+
diff --git a/test/unit/event/event_with_human_name_test.rb b/test/unit/event/event_with_human_name_test.rb
new file mode 100644
index 0000000..9254ac7
--- /dev/null
+++ b/test/unit/event/event_with_human_name_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+
+class EventWithHumanNameTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite, human_name: 'start')
+  end
+
+  def test_should_use_custom_human_name
+    assert_equal 'start', @event.human_name
+  end
+end
diff --git a/test/unit/event/event_with_invalid_current_state_test.rb b/test/unit/event/event_with_invalid_current_state_test.rb
new file mode 100644
index 0000000..c754595
--- /dev/null
+++ b/test/unit/event/event_with_invalid_current_state_test.rb
@@ -0,0 +1,30 @@
+require_relative '../../test_helper'
+
+class EventWithInvalidCurrentStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(parked: :idling)
+
+    @object = @klass.new
+    @object.state = 'invalid'
+  end
+
+  def test_should_raise_exception_when_checking_availability
+    exception = assert_raises(ArgumentError) { @event.can_fire?(@object) }
+    assert_equal '"invalid" is not a known state value', exception.message
+  end
+
+  def test_should_raise_exception_when_finding_transition
+    exception = assert_raises(ArgumentError) { @event.transition_for(@object) }
+    assert_equal '"invalid" is not a known state value', exception.message
+  end
+
+  def test_should_raise_exception_when_firing
+    exception = assert_raises(ArgumentError) { @event.fire(@object) }
+    assert_equal '"invalid" is not a known state value', exception.message
+  end
+end
diff --git a/test/unit/event/event_with_machine_action_test.rb b/test/unit/event/event_with_machine_action_test.rb
new file mode 100644
index 0000000..476917c
--- /dev/null
+++ b/test/unit/event/event_with_machine_action_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class EventWithMachineActionTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :saved
+
+      def save
+        @saved = true
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(parked: :idling)
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_run_action_on_fire
+    @event.fire(@object)
+    assert @object.saved
+  end
+
+  def test_should_not_run_action_if_configured_to_skip
+    @event.fire(@object, false)
+    refute @object.saved
+  end
+end
+
diff --git a/test/unit/event/event_with_marshalling_test.rb b/test/unit/event/event_with_marshalling_test.rb
new file mode 100644
index 0000000..63d0c56
--- /dev/null
+++ b/test/unit/event/event_with_marshalling_test.rb
@@ -0,0 +1,47 @@
+require_relative '../../test_helper'
+
+class EventWithMarshallingTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+        true
+      end
+    end
+    self.class.const_set('Example', @klass)
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(parked: :idling)
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_marshal_during_before_callbacks
+    @machine.before_transition { |object, _transition| Marshal.dump(object) }
+    @event.fire(@object)
+  end
+
+  def test_should_marshal_during_action
+    @klass.class_eval do
+      remove_method :save
+
+      def save
+        Marshal.dump(self)
+      end
+    end
+
+    @event.fire(@object)
+  end
+
+  def test_should_marshal_during_after_callbacks
+    @machine.after_transition { |object, _transition| Marshal.dump(object) }
+    @event.fire(@object)
+  end
+
+  def teardown
+    self.class.send(:remove_const, 'Example')
+  end
+end
diff --git a/test/unit/event/event_with_matching_disabled_transitions_test.rb b/test/unit/event/event_with_matching_disabled_transitions_test.rb
new file mode 100644
index 0000000..a1e486d
--- /dev/null
+++ b/test/unit/event/event_with_matching_disabled_transitions_test.rb
@@ -0,0 +1,115 @@
+require_relative '../../test_helper'
+
+class EventWithMatchingDisabledTransitionsTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def invalidate(object, _attribute, message, values = [])
+      (object.errors ||= []) << generate_message(message, values)
+    end
+
+    def reset(object)
+      object.errors = []
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(EventWithMatchingDisabledTransitionsTest::Custom)
+
+    @klass = Class.new do
+      attr_accessor :errors
+    end
+
+    @machine = StateMachines::Machine.new(@klass, integration: :custom)
+    @machine.state :parked, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(parked: :idling, if: lambda { false })
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_not_be_able_to_fire
+    refute @event.can_fire?(@object)
+  end
+
+  def test_should_be_able_to_fire_with_disabled_guards
+    assert @event.can_fire?(@object, guard: false)
+  end
+
+  def test_should_not_have_a_transition
+    assert_nil @event.transition_for(@object)
+  end
+
+  def test_should_have_a_transition_with_disabled_guards
+    refute_nil @event.transition_for(@object, guard: false)
+  end
+
+  def test_should_not_fire
+    refute @event.fire(@object)
+  end
+
+  def test_should_not_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_invalidate_the_state
+    @event.fire(@object)
+    assert_equal ['cannot transition via "ignite"'], @object.errors
+  end
+
+  def test_should_invalidate_with_human_event_name
+    @event.human_name = 'start'
+    @event.fire(@object)
+    assert_equal ['cannot transition via "start"'], @object.errors
+  end
+
+  def test_should_invalid_with_human_state_name_if_specified
+    klass = Class.new do
+      attr_accessor :errors
+    end
+
+    machine = StateMachines::Machine.new(klass, integration: :custom, messages: { invalid_transition: 'cannot transition via "%s" from "%s"' })
+    parked, idling = machine.state :parked, :idling
+    parked.human_name = 'stopped'
+
+    machine.events << event = StateMachines::Event.new(machine, :ignite)
+    event.transition(parked: :idling, if: lambda { false })
+
+    object = @klass.new
+    object.state = 'parked'
+
+    event.fire(object)
+    assert_equal ['cannot transition via "ignite" from "stopped"'], object.errors
+  end
+
+  def test_should_reset_existing_error
+    @object.errors = ['invalid']
+
+    @event.fire(@object)
+    assert_equal ['cannot transition via "ignite"'], @object.errors
+  end
+
+  def test_should_run_failure_callbacks
+    callback_args = nil
+    @machine.after_failure { |*args| callback_args = args }
+
+    @event.fire(@object)
+
+    object, transition = callback_args
+    assert_equal @object, object
+    refute_nil transition
+    assert_equal @object, transition.object
+    assert_equal @machine, transition.machine
+    assert_equal :ignite, transition.event
+    assert_equal :parked, transition.from_name
+    assert_equal :parked, transition.to_name
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
+
diff --git a/test/unit/event/event_with_matching_enabled_transitions_test.rb b/test/unit/event/event_with_matching_enabled_transitions_test.rb
new file mode 100644
index 0000000..f8d9e55
--- /dev/null
+++ b/test/unit/event/event_with_matching_enabled_transitions_test.rb
@@ -0,0 +1,75 @@
+require_relative '../../test_helper'
+
+class EventWithMatchingEnabledTransitionsTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def invalidate(object, _attribute, message, values = [])
+      (object.errors ||= []) << generate_message(message, values)
+    end
+
+    def reset(object)
+      object.errors = []
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(EventWithMatchingEnabledTransitionsTest::Custom)
+
+    @klass = Class.new do
+      attr_accessor :errors
+    end
+
+    @machine = StateMachines::Machine.new(@klass, integration: :custom)
+    @machine.state :parked, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(parked: :idling)
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'idling', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_fire
+    assert @event.fire(@object)
+  end
+
+  def test_should_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_reset_existing_error
+    @object.errors = ['invalid']
+
+    @event.fire(@object)
+    assert_equal [], @object.errors
+  end
+
+  def test_should_not_invalidate_the_state
+    @event.fire(@object)
+    assert_equal [], @object.errors
+  end
+
+  def test_should_not_be_able_to_fire_on_reset
+    @event.reset
+    refute @event.can_fire?(@object)
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
+
diff --git a/test/unit/event/event_with_multiple_transitions_test.rb b/test/unit/event/event_with_multiple_transitions_test.rb
new file mode 100644
index 0000000..49198ee
--- /dev/null
+++ b/test/unit/event/event_with_multiple_transitions_test.rb
@@ -0,0 +1,61 @@
+require_relative '../../test_helper'
+
+class EventWithMultipleTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(idling: :idling)
+    @event.transition(parked: :idling)
+    @event.transition(parked: :parked)
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'idling', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_allow_specific_transition_selection_using_from
+    transition = @event.transition_for(@object, from: :idling)
+
+    refute_nil transition
+    assert_equal 'idling', transition.from
+    assert_equal 'idling', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_allow_specific_transition_selection_using_to
+    transition = @event.transition_for(@object, from: :parked, to: :parked)
+
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'parked', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_not_allow_specific_transition_selection_using_on
+    exception = assert_raises(ArgumentError) { @event.transition_for(@object, on: :park) }
+    assert_equal 'Unknown key: :on. Valid keys are: :from, :to, :guard', exception.message
+  end
+
+  def test_should_fire
+    assert @event.fire(@object)
+  end
+
+  def test_should_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'idling', @object.state
+  end
+end
diff --git a/test/unit/event/event_with_namespace_test.rb b/test/unit/event/event_with_namespace_test.rb
new file mode 100644
index 0000000..39ea26f
--- /dev/null
+++ b/test/unit/event/event_with_namespace_test.rb
@@ -0,0 +1,34 @@
+require_relative '../../test_helper'
+
+class EventWithNamespaceTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, namespace: 'alarm')
+    @machine.events << @event = StateMachines::Event.new(@machine, :enable)
+    @object = @klass.new
+  end
+
+  def test_should_have_a_name
+    assert_equal :enable, @event.name
+  end
+
+  def test_should_have_a_qualified_name
+    assert_equal :enable_alarm, @event.qualified_name
+  end
+
+  def test_should_namespace_predicate
+    assert @object.respond_to?(:can_enable_alarm?)
+  end
+
+  def test_should_namespace_transition_accessor
+    assert @object.respond_to?(:enable_alarm_transition)
+  end
+
+  def test_should_namespace_action
+    assert @object.respond_to?(:enable_alarm)
+  end
+
+  def test_should_namespace_bang_action
+    assert @object.respond_to?(:enable_alarm!)
+  end
+end
diff --git a/test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb b/test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb
new file mode 100644
index 0000000..eae5de6
--- /dev/null
+++ b/test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb
@@ -0,0 +1,60 @@
+require_relative '../../test_helper'
+class EventWithTransitionWithBlacklistedToStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :parked, :idling, :first_gear, :second_gear
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(from: :parked, to: StateMachines::BlacklistMatcher.new([:parked, :idling]))
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'first_gear', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_allow_loopback_first_when_possible
+    @event.transition(from: :second_gear, to: StateMachines::BlacklistMatcher.new([:parked, :idling]))
+    @object.state = 'second_gear'
+
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'second_gear', transition.from
+    assert_equal 'second_gear', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_allow_specific_transition_selection_using_to
+    transition = @event.transition_for(@object, from: :parked, to: :second_gear)
+
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'second_gear', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_not_allow_transition_selection_if_not_matching
+    transition = @event.transition_for(@object, from: :parked, to: :parked)
+    assert_nil transition
+  end
+
+  def test_should_fire
+    assert @event.fire(@object)
+  end
+
+  def test_should_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'first_gear', @object.state
+  end
+end
diff --git a/test/unit/event/event_with_transition_with_loopback_state_test.rb b/test/unit/event/event_with_transition_with_loopback_state_test.rb
new file mode 100644
index 0000000..9dfabfa
--- /dev/null
+++ b/test/unit/event/event_with_transition_with_loopback_state_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class EventWithTransitionWithLoopbackStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :park)
+    @event.transition(from: :parked, to: StateMachines::LoopbackMatcher.instance)
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'parked', transition.to
+    assert_equal :park, transition.event
+  end
+
+  def test_should_fire
+    assert @event.fire(@object)
+  end
+
+  def test_should_not_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'parked', @object.state
+  end
+end
diff --git a/test/unit/event/event_with_transition_with_nil_to_state_test.rb b/test/unit/event/event_with_transition_with_nil_to_state_test.rb
new file mode 100644
index 0000000..b57a0d8
--- /dev/null
+++ b/test/unit/event/event_with_transition_with_nil_to_state_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class EventWithTransitionWithNilToStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state nil, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :park)
+    @event.transition(idling: nil)
+
+    @object = @klass.new
+    @object.state = 'idling'
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'idling', transition.from
+    assert_equal nil, transition.to
+    assert_equal :park, transition.event
+  end
+
+  def test_should_fire
+    assert @event.fire(@object)
+  end
+
+  def test_should_not_change_the_current_state
+    @event.fire(@object)
+    assert_equal nil, @object.state
+  end
+end
diff --git a/test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb b/test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb
new file mode 100644
index 0000000..3a48481
--- /dev/null
+++ b/test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb
@@ -0,0 +1,51 @@
+require_relative '../../test_helper'
+
+class EventWithTransitionWithWhitelistedToStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :parked, :idling, :first_gear, :second_gear
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(from: :parked, to: StateMachines::WhitelistMatcher.new([:first_gear, :second_gear]))
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'first_gear', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_allow_specific_transition_selection_using_to
+    transition = @event.transition_for(@object, from: :parked, to: :second_gear)
+
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'second_gear', transition.to
+    assert_equal :ignite, transition.event
+  end
+
+  def test_should_not_allow_transition_selection_if_not_matching
+    transition = @event.transition_for(@object, from: :parked, to: :parked)
+    assert_nil transition
+  end
+
+  def test_should_fire
+    assert @event.fire(@object)
+  end
+
+  def test_should_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'first_gear', @object.state
+  end
+end
+
diff --git a/test/unit/event/event_with_transition_without_to_state_test.rb b/test/unit/event/event_with_transition_without_to_state_test.rb
new file mode 100644
index 0000000..c065dff
--- /dev/null
+++ b/test/unit/event/event_with_transition_without_to_state_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class EventWithTransitionWithoutToStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :park)
+    @event.transition(from: :parked)
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'parked', transition.to
+    assert_equal :park, transition.event
+  end
+
+  def test_should_fire
+    assert @event.fire(@object)
+  end
+
+  def test_should_not_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'parked', @object.state
+  end
+end
diff --git a/test/unit/event/event_with_transitions_test.rb b/test/unit/event/event_with_transitions_test.rb
new file mode 100644
index 0000000..d73154a
--- /dev/null
+++ b/test/unit/event/event_with_transitions_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class EventWithTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(parked: :idling)
+    @event.transition(first_gear: :idling)
+  end
+
+  def test_should_include_all_transition_states_in_known_states
+    assert_equal [:parked, :idling, :first_gear], @event.known_states
+  end
+
+  def test_should_include_new_transition_states_after_calling_known_states
+    @event.known_states
+    @event.transition(stalled: :idling)
+
+    assert_equal [:parked, :idling, :first_gear, :stalled], @event.known_states
+  end
+
+  def test_should_clear_known_states_on_reset
+    @event.reset
+    assert_equal [], @event.known_states
+  end
+
+  def test_should_use_pretty_inspect
+    assert_match '#<StateMachines::Event name=:ignite transitions=[:parked => :idling, :first_gear => :idling]>', @event.inspect
+  end
+end
+
diff --git a/test/unit/event/event_without_matching_transitions_test.rb b/test/unit/event/event_without_matching_transitions_test.rb
new file mode 100644
index 0000000..8b055aa
--- /dev/null
+++ b/test/unit/event/event_without_matching_transitions_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class EventWithoutMatchingTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @event.transition(parked: :idling)
+
+    @object = @klass.new
+    @object.state = 'idling'
+  end
+
+  def test_should_not_be_able_to_fire
+    refute @event.can_fire?(@object)
+  end
+
+  def test_should_be_able_to_fire_with_custom_from_state
+    assert @event.can_fire?(@object, from: :parked)
+  end
+
+  def test_should_not_have_a_transition
+    assert_nil @event.transition_for(@object)
+  end
+
+  def test_should_have_a_transition_with_custom_from_state
+    refute_nil @event.transition_for(@object, from: :parked)
+  end
+
+  def test_should_not_fire
+    refute @event.fire(@object)
+  end
+
+  def test_should_not_change_the_current_state
+    @event.fire(@object)
+    assert_equal 'idling', @object.state
+  end
+end
+
diff --git a/test/unit/event/event_without_transitions_test.rb b/test/unit/event/event_without_transitions_test.rb
new file mode 100644
index 0000000..20e9d10
--- /dev/null
+++ b/test/unit/event/event_without_transitions_test.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class EventWithoutTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.events << @event = StateMachines::Event.new(@machine, :ignite)
+    @object = @klass.new
+  end
+
+  def test_should_not_be_able_to_fire
+    refute @event.can_fire?(@object)
+  end
+
+  def test_should_not_have_a_transition
+    assert_nil @event.transition_for(@object)
+  end
+
+  def test_should_not_fire
+    refute @event.fire(@object)
+  end
+
+  def test_should_not_change_the_current_state
+    @event.fire(@object)
+    assert_nil @object.state
+  end
+end
+
diff --git a/test/unit/event/invalid_event_test.rb b/test/unit/event/invalid_event_test.rb
new file mode 100644
index 0000000..ba26f52
--- /dev/null
+++ b/test/unit/event/invalid_event_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class InvalidEventTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @invalid_event = StateMachines::InvalidEvent.new(@object, :invalid)
+  end
+
+  def test_should_have_an_object
+    assert_equal @object, @invalid_event.object
+  end
+
+  def test_should_have_an_event
+    assert_equal :invalid, @invalid_event.event
+  end
+
+  def test_should_generate_a_message
+    assert_equal ':invalid is an unknown state machine event', @invalid_event.message
+  end
+end
diff --git a/test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb b/test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb
new file mode 100644
index 0000000..520afcd
--- /dev/null
+++ b/test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb
@@ -0,0 +1,62 @@
+require_relative '../../test_helper'
+
+class EventCollectionAttributeWithMachineActionTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @events = StateMachines::EventCollection.new(@machine)
+
+    @machine.state :parked, :idling
+    @events << @ignite = StateMachines::Event.new(@machine, :ignite)
+    @machine.events.concat(@events)
+
+    @object = @klass.new
+  end
+
+  def test_should_not_have_transition_if_nil
+    @object.state_event = nil
+    assert_nil @events.attribute_transition_for(@object)
+  end
+
+  def test_should_not_have_transition_if_empty
+    @object.state_event = ''
+    assert_nil @events.attribute_transition_for(@object)
+  end
+
+  def test_should_have_invalid_transition_if_invalid_event_specified
+    @object.state_event = 'invalid'
+    assert_equal false, @events.attribute_transition_for(@object)
+  end
+
+  def test_should_have_invalid_transition_if_event_cannot_be_fired
+    @object.state_event = 'ignite'
+    assert_equal false, @events.attribute_transition_for(@object)
+  end
+
+  def test_should_have_valid_transition_if_event_can_be_fired
+    @ignite.transition parked: :idling
+    @object.state_event = 'ignite'
+
+    assert_instance_of StateMachines::Transition, @events.attribute_transition_for(@object)
+  end
+
+  def test_should_have_valid_transition_if_already_defined_in_transition_cache
+    @ignite.transition parked: :idling
+    @object.state_event = nil
+    @object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
+
+    assert_equal transition, @events.attribute_transition_for(@object)
+  end
+
+  def test_should_use_transition_cache_if_both_event_and_transition_are_present
+    @ignite.transition parked: :idling
+    @object.state_event = 'ignite'
+    @object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
+
+    assert_equal transition, @events.attribute_transition_for(@object)
+  end
+end
diff --git a/test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb b/test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb
new file mode 100644
index 0000000..2874693
--- /dev/null
+++ b/test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+
+class EventCollectionAttributeWithNamespacedMachineTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, namespace: 'alarm', initial: :active, action: :save)
+    @events = StateMachines::EventCollection.new(@machine)
+
+    @machine.state :active, :off
+    @events << @disable = StateMachines::Event.new(@machine, :disable)
+    @machine.events.concat(@events)
+
+    @object = @klass.new
+  end
+
+  def test_should_not_have_transition_if_nil
+    @object.state_event = nil
+    assert_nil @events.attribute_transition_for(@object)
+  end
+
+  def test_should_have_invalid_transition_if_event_cannot_be_fired
+    @object.state_event = 'disable'
+    assert_equal false, @events.attribute_transition_for(@object)
+  end
+
+  def test_should_have_valid_transition_if_event_can_be_fired
+    @disable.transition active: :off
+    @object.state_event = 'disable'
+
+    assert_instance_of StateMachines::Transition, @events.attribute_transition_for(@object)
+  end
+end
diff --git a/test/unit/event_collection/event_collection_by_default_test.rb b/test/unit/event_collection/event_collection_by_default_test.rb
new file mode 100644
index 0000000..0fe5709
--- /dev/null
+++ b/test/unit/event_collection/event_collection_by_default_test.rb
@@ -0,0 +1,26 @@
+require_relative '../../test_helper'
+
+class EventCollectionByDefaultTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @events = StateMachines::EventCollection.new(@machine)
+    @object = @klass.new
+  end
+
+  def test_should_not_have_any_nodes
+    assert_equal 0, @events.length
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @events.machine
+  end
+
+  def test_should_not_have_any_valid_events_for_an_object
+    assert @events.valid_for(@object).empty?
+  end
+
+  def test_should_not_have_any_transitions_for_an_object
+    assert @events.transitions_for(@object).empty?
+  end
+end
diff --git a/test/unit/event_collection/event_collection_test.rb b/test/unit/event_collection/event_collection_test.rb
new file mode 100644
index 0000000..65d60c3
--- /dev/null
+++ b/test/unit/event_collection/event_collection_test.rb
@@ -0,0 +1,39 @@
+require_relative '../../test_helper'
+
+class EventCollectionTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new, namespace: 'alarm')
+    @events = StateMachines::EventCollection.new(machine)
+
+    @events << @open = StateMachines::Event.new(machine, :enable)
+    machine.events.concat(@events)
+  end
+
+  def test_should_index_by_name
+    assert_equal @open, @events[:enable, :name]
+  end
+
+  def test_should_index_by_name_by_default
+    assert_equal @open, @events[:enable]
+  end
+
+  def test_should_index_by_string_name
+    assert_equal @open, @events['enable']
+  end
+
+  def test_should_index_by_qualified_name
+    assert_equal @open, @events[:enable_alarm, :qualified_name]
+  end
+
+  def test_should_index_by_string_qualified_name
+    assert_equal @open, @events['enable_alarm', :qualified_name]
+  end
+end
+
+
+
+
+
+
+
+
diff --git a/test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb b/test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb
new file mode 100644
index 0000000..b0b25cd
--- /dev/null
+++ b/test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class EventCollectionWithCustomMachineAttributeTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, :state, attribute: :state_id, initial: :parked, action: :save)
+    @events = StateMachines::EventCollection.new(@machine)
+
+    @machine.state :parked, :idling
+    @events << @ignite = StateMachines::Event.new(@machine, :ignite)
+    @machine.events.concat(@events)
+
+    @object = @klass.new
+  end
+
+  def test_should_not_have_transition_if_nil
+    @object.state_event = nil
+    assert_nil @events.attribute_transition_for(@object)
+  end
+
+  def test_should_have_valid_transition_if_event_can_be_fired
+    @ignite.transition parked: :idling
+    @object.state_event = 'ignite'
+
+    assert_instance_of StateMachines::Transition, @events.attribute_transition_for(@object)
+  end
+end
diff --git a/test/unit/event_collection/event_collection_with_events_with_transitions_test.rb b/test/unit/event_collection/event_collection_with_events_with_transitions_test.rb
new file mode 100644
index 0000000..6bc3b7a
--- /dev/null
+++ b/test/unit/event_collection/event_collection_with_events_with_transitions_test.rb
@@ -0,0 +1,76 @@
+require_relative '../../test_helper'
+
+class EventCollectionWithEventsWithTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @events = StateMachines::EventCollection.new(@machine)
+
+    @machine.state :idling, :first_gear
+
+    @events << @ignite = StateMachines::Event.new(@machine, :ignite)
+    @ignite.transition parked: :idling
+
+    @events << @park = StateMachines::Event.new(@machine, :park)
+    @park.transition idling: :parked
+
+    @events << @shift_up = StateMachines::Event.new(@machine, :shift_up)
+    @shift_up.transition parked: :first_gear
+    @shift_up.transition idling: :first_gear, if: lambda { false }
+
+    @machine.events.concat(@events)
+
+    @object = @klass.new
+  end
+
+  def test_should_find_valid_events_based_on_current_state
+    assert_equal [@ignite, @shift_up], @events.valid_for(@object)
+  end
+
+  def test_should_filter_valid_events_by_from_state
+    assert_equal [@park], @events.valid_for(@object, from: :idling)
+  end
+
+  def test_should_filter_valid_events_by_to_state
+    assert_equal [@shift_up], @events.valid_for(@object, to: :first_gear)
+  end
+
+  def test_should_filter_valid_events_by_event
+    assert_equal [@ignite], @events.valid_for(@object, on: :ignite)
+  end
+
+  def test_should_filter_valid_events_by_multiple_requirements
+    assert_equal [], @events.valid_for(@object, from: :idling, to: :first_gear)
+  end
+
+  def test_should_allow_finding_valid_events_without_guards
+    assert_equal [@shift_up], @events.valid_for(@object, from: :idling, to: :first_gear, guard: false)
+  end
+
+  def test_should_find_valid_transitions_based_on_current_state
+    assert_equal [
+                     StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)
+                 ], @events.transitions_for(@object)
+  end
+
+  def test_should_filter_valid_transitions_by_from_state
+    assert_equal [StateMachines::Transition.new(@object, @machine, :park, :idling, :parked)], @events.transitions_for(@object, from: :idling)
+  end
+
+  def test_should_filter_valid_transitions_by_to_state
+    assert_equal [StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)], @events.transitions_for(@object, to: :first_gear)
+  end
+
+  def test_should_filter_valid_transitions_by_event
+    assert_equal [StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)], @events.transitions_for(@object, on: :ignite)
+  end
+
+  def test_should_filter_valid_transitions_by_multiple_requirements
+    assert_equal [], @events.transitions_for(@object, from: :idling, to: :first_gear)
+  end
+
+  def test_should_allow_finding_valid_transitions_without_guards
+    assert_equal [StateMachines::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)], @events.transitions_for(@object, from: :idling, to: :first_gear, guard: false)
+  end
+end
diff --git a/test/unit/event_collection/event_collection_with_multiple_events_test.rb b/test/unit/event_collection/event_collection_with_multiple_events_test.rb
new file mode 100644
index 0000000..45508b1
--- /dev/null
+++ b/test/unit/event_collection/event_collection_with_multiple_events_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class EventCollectionWithMultipleEventsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @events = StateMachines::EventCollection.new(@machine)
+
+    @machine.state :first_gear
+    @park, @shift_down = @machine.event :park, :shift_down
+
+    @events << @park
+    @park.transition first_gear: :parked
+
+    @events << @shift_down
+    @shift_down.transition first_gear: :parked
+
+    @machine.events.concat(@events)
+  end
+
+  def test_should_only_include_all_valid_events_for_an_object
+    object = @klass.new
+    object.state = 'first_gear'
+    assert_equal [@park, @shift_down], @events.valid_for(object)
+  end
+end
+
diff --git a/test/unit/event_collection/event_collection_with_validations_test.rb b/test/unit/event_collection/event_collection_with_validations_test.rb
new file mode 100644
index 0000000..d42e27a
--- /dev/null
+++ b/test/unit/event_collection/event_collection_with_validations_test.rb
@@ -0,0 +1,74 @@
+require_relative '../../test_helper'
+
+class EventCollectionWithValidationsTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def invalidate(object, _attribute, message, values = [])
+      (object.errors ||= []) << generate_message(message, values)
+    end
+
+    def reset(object)
+      object.errors = []
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(EventCollectionWithValidationsTest::Custom)
+
+    @klass = Class.new do
+      attr_accessor :errors
+
+      def initialize
+        @errors = []
+        super
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save, integration: :custom)
+    @events = StateMachines::EventCollection.new(@machine)
+
+    @parked, @idling = @machine.state :parked, :idling
+    @events << @ignite = StateMachines::Event.new(@machine, :ignite)
+    @machine.events.concat(@events)
+
+    @object = @klass.new
+  end
+
+  def test_should_invalidate_if_invalid_event_specified
+    @object.state_event = 'invalid'
+    @events.attribute_transition_for(@object, true)
+
+    assert_equal ['is invalid'], @object.errors
+  end
+
+  def test_should_invalidate_if_event_cannot_be_fired
+    @object.state = 'idling'
+    @object.state_event = 'ignite'
+    @events.attribute_transition_for(@object, true)
+
+    assert_equal ['cannot transition when idling'], @object.errors
+  end
+
+  def test_should_invalidate_with_human_name_if_invalid_event_specified
+    @idling.human_name = 'waiting'
+    @object.state = 'idling'
+    @object.state_event = 'ignite'
+    @events.attribute_transition_for(@object, true)
+
+    assert_equal ['cannot transition when waiting'], @object.errors
+  end
+
+  def test_should_not_invalidate_event_can_be_fired
+    @ignite.transition parked: :idling
+    @object.state_event = 'ignite'
+    @events.attribute_transition_for(@object, true)
+
+    assert_equal [], @object.errors
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
+
diff --git a/test/unit/event_collection/event_collection_without_machine_action_test.rb b/test/unit/event_collection/event_collection_without_machine_action_test.rb
new file mode 100644
index 0000000..6d005ff
--- /dev/null
+++ b/test/unit/event_collection/event_collection_without_machine_action_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class EventCollectionWithoutMachineActionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @events = StateMachines::EventCollection.new(@machine)
+    @events << StateMachines::Event.new(@machine, :ignite)
+    @machine.events.concat(@events)
+
+    @object = @klass.new
+  end
+
+  def test_should_not_have_an_attribute_transition
+    assert_nil @events.attribute_transition_for(@object)
+  end
+end
+
diff --git a/test/unit/event_collection/event_string_collection_test.rb b/test/unit/event_collection/event_string_collection_test.rb
new file mode 100644
index 0000000..e1abe4f
--- /dev/null
+++ b/test/unit/event_collection/event_string_collection_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class EventStringCollectionTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new, namespace: 'alarm')
+    @events = StateMachines::EventCollection.new(machine)
+
+    @events << @open = StateMachines::Event.new(machine, 'enable')
+    machine.events.concat(@events)
+  end
+
+  def test_should_index_by_name
+    assert_equal @open, @events['enable', :name]
+  end
+
+  def test_should_index_by_name_by_default
+    assert_equal @open, @events['enable']
+  end
+
+  def test_should_index_by_symbol_name
+    assert_equal @open, @events[:enable]
+  end
+
+  def test_should_index_by_qualified_name
+    assert_equal @open, @events['enable_alarm', :qualified_name]
+  end
+
+  def test_should_index_by_symbol_qualified_name
+    assert_equal @open, @events[:enable_alarm, :qualified_name]
+  end
+end
diff --git a/test/unit/helper_module_test.rb b/test/unit/helper_module_test.rb
new file mode 100644
index 0000000..18b5656
--- /dev/null
+++ b/test/unit/helper_module_test.rb
@@ -0,0 +1,17 @@
+require_relative '../test_helper'
+
+class HelperModuleTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @helper_module = StateMachines::HelperModule.new(@machine, :instance)
+  end
+
+  def test_should_not_have_a_name
+    assert_equal '', @helper_module.name.to_s
+  end
+
+  def test_should_provide_human_readable_to_s
+    assert_equal "#{@klass} :state instance helpers", @helper_module.to_s
+  end
+end
diff --git a/test/unit/integrations/integration_finder_test.rb b/test/unit/integrations/integration_finder_test.rb
new file mode 100644
index 0000000..dd89fab
--- /dev/null
+++ b/test/unit/integrations/integration_finder_test.rb
@@ -0,0 +1,16 @@
+require_relative '../../test_helper'
+
+class IntegrationFinderTest < StateMachinesTest
+  def setup
+    StateMachines::Integrations.reset
+  end
+
+  def test_should_raise_an_exception_if_invalid
+    exception = assert_raises(StateMachines::IntegrationNotFound) { StateMachines::Integrations.find_by_name(:invalid) }
+    assert_equal ':invalid is an invalid integration. No integrations registered', exception.message
+  end
+
+  def test_should_have_no_integrations
+    assert_equal([], StateMachines::Integrations.list)
+  end
+end
diff --git a/test/unit/integrations/integration_matcher_test.rb b/test/unit/integrations/integration_matcher_test.rb
new file mode 100644
index 0000000..993fdae
--- /dev/null
+++ b/test/unit/integrations/integration_matcher_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+require_relative '../../files/models/vehicle'
+require_relative '../../files/integrations/vehicle'
+
+class IntegrationMatcherTest < StateMachinesTest
+  def setup
+    StateMachines::Integrations.reset
+  end
+
+  def test_should_return_nil_if_no_match_found
+    assert_nil StateMachines::Integrations.match(Vehicle)
+  end
+
+  def test_should_return_integration_class_if_match_found
+    StateMachines::Integrations.register(VehicleIntegration)
+    assert_equal VehicleIntegration, StateMachines::Integrations.match(Vehicle)
+  end
+
+  def test_should_return_nil_if_no_match_found_with_ancestors
+    assert_nil StateMachines::Integrations.match_ancestors(['Fake'])
+  end
+
+  def test_should_return_integration_class_if_match_found_with_ancestors
+    StateMachines::Integrations.register(VehicleIntegration)
+    assert_equal VehicleIntegration, StateMachines::Integrations.match_ancestors(['Fake', 'Vehicle'])
+  end
+end
diff --git a/test/unit/invalid_transition/invalid_parallel_transition_test.rb b/test/unit/invalid_transition/invalid_parallel_transition_test.rb
new file mode 100644
index 0000000..19d07a4
--- /dev/null
+++ b/test/unit/invalid_transition/invalid_parallel_transition_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class InvalidParallelTransitionTest < StateMachinesTest
+  def setup
+    @object = Object.new
+    @events = [:ignite, :disable_alarm]
+
+    @invalid_transition = StateMachines::InvalidParallelTransition.new(@object, @events)
+  end
+
+  def test_should_have_an_object
+    assert_equal @object, @invalid_transition.object
+  end
+
+  def test_should_have_events
+    assert_equal @events, @invalid_transition.events
+  end
+end
diff --git a/test/unit/invalid_transition/invalid_transition_test.rb b/test/unit/invalid_transition/invalid_transition_test.rb
new file mode 100644
index 0000000..2682cd7
--- /dev/null
+++ b/test/unit/invalid_transition/invalid_transition_test.rb
@@ -0,0 +1,47 @@
+require_relative '../../test_helper'
+
+class InvalidTransitionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @state = @machine.state :parked
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :ignite)
+  end
+
+  def test_should_have_an_object
+    assert_equal @object, @invalid_transition.object
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @invalid_transition.machine
+  end
+
+  def test_should_have_an_event
+    assert_equal :ignite, @invalid_transition.event
+  end
+
+  def test_should_have_a_qualified_event
+    assert_equal :ignite, @invalid_transition.qualified_event
+  end
+
+  def test_should_have_a_from_value
+    assert_equal 'parked', @invalid_transition.from
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :parked, @invalid_transition.from_name
+  end
+
+  def test_should_have_a_qualified_from_name
+    assert_equal :parked, @invalid_transition.qualified_from_name
+  end
+
+  def test_should_generate_a_message
+    assert_equal 'Cannot transition state via :ignite from :parked', @invalid_transition.message
+  end
+end
diff --git a/test/unit/invalid_transition/invalid_transition_with_integration_test.rb b/test/unit/invalid_transition/invalid_transition_with_integration_test.rb
new file mode 100644
index 0000000..1ab5489
--- /dev/null
+++ b/test/unit/invalid_transition/invalid_transition_with_integration_test.rb
@@ -0,0 +1,45 @@
+require_relative '../../test_helper'
+
+class InvalidTransitionWithIntegrationTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def errors_for(object)
+      object.errors
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(InvalidTransitionWithIntegrationTest::Custom)
+
+    @klass = Class.new do
+      attr_accessor :errors
+    end
+    @machine = StateMachines::Machine.new(@klass, integration: :custom)
+    @machine.state :parked
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def fix_test
+    skip
+  end
+
+  def test_should_generate_a_message_without_reasons_if_empty
+    @object.errors = ''
+    invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :ignite)
+    assert_equal 'Cannot transition state via :ignite from :parked', invalid_transition.message
+  end
+
+  def test_should_generate_a_message_with_error_reasons_if_errors_found
+    @object.errors = 'Id is invalid, Name is invalid'
+    invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :ignite)
+    assert_equal 'Cannot transition state via :ignite from :parked (Reason(s): Id is invalid, Name is invalid)', invalid_transition.message
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/invalid_transition/invalid_transition_with_namespace_test.rb b/test/unit/invalid_transition/invalid_transition_with_namespace_test.rb
new file mode 100644
index 0000000..88e0c20
--- /dev/null
+++ b/test/unit/invalid_transition/invalid_transition_with_namespace_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class InvalidTransitionWithNamespaceTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, namespace: 'alarm')
+    @state = @machine.state :active
+    @machine.event :disable
+
+    @object = @klass.new
+    @object.state = 'active'
+
+    @invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :disable)
+  end
+
+  def test_should_have_an_event
+    assert_equal :disable, @invalid_transition.event
+  end
+
+  def test_should_have_a_qualified_event
+    assert_equal :disable_alarm, @invalid_transition.qualified_event
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :active, @invalid_transition.from_name
+  end
+
+  def test_should_have_a_qualified_from_name
+    assert_equal :alarm_active, @invalid_transition.qualified_from_name
+  end
+end
+
diff --git a/test/unit/machine/machine_after_being_copied_test.rb b/test/unit/machine/machine_after_being_copied_test.rb
new file mode 100644
index 0000000..98ff08b
--- /dev/null
+++ b/test/unit/machine/machine_after_being_copied_test.rb
@@ -0,0 +1,62 @@
+require_relative '../../test_helper'
+
+class MachineAfterBeingCopiedTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new, :state, initial: :parked)
+    @machine.event(:ignite) {}
+    @machine.before_transition(lambda {})
+    @machine.after_transition(lambda {})
+    @machine.around_transition(lambda {})
+    @machine.after_failure(lambda {})
+
+    @copied_machine = @machine.clone
+  end
+
+  def test_should_not_have_the_same_collection_of_states
+    refute_same @copied_machine.states, @machine.states
+  end
+
+  def test_should_copy_each_state
+    refute_same @copied_machine.states[:parked], @machine.states[:parked]
+  end
+
+  def test_should_update_machine_for_each_state
+    assert_equal @copied_machine, @copied_machine.states[:parked].machine
+  end
+
+  def test_should_not_update_machine_for_original_state
+    assert_equal @machine, @machine.states[:parked].machine
+  end
+
+  def test_should_not_have_the_same_collection_of_events
+    refute_same @copied_machine.events, @machine.events
+  end
+
+  def test_should_copy_each_event
+    refute_same @copied_machine.events[:ignite], @machine.events[:ignite]
+  end
+
+  def test_should_update_machine_for_each_event
+    assert_equal @copied_machine, @copied_machine.events[:ignite].machine
+  end
+
+  def test_should_not_update_machine_for_original_event
+    assert_equal @machine, @machine.events[:ignite].machine
+  end
+
+  def test_should_not_have_the_same_callbacks
+    refute_same @copied_machine.callbacks, @machine.callbacks
+  end
+
+  def test_should_not_have_the_same_before_callbacks
+    refute_same @copied_machine.callbacks[:before], @machine.callbacks[:before]
+  end
+
+  def test_should_not_have_the_same_after_callbacks
+    refute_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
+  end
+
+  def test_should_not_have_the_same_failure_callbacks
+    refute_same @copied_machine.callbacks[:failure], @machine.callbacks[:failure]
+  end
+end
diff --git a/test/unit/machine/machine_after_changing_initial_state.rb b/test/unit/machine/machine_after_changing_initial_state.rb
new file mode 100644
index 0000000..1ed79ca
--- /dev/null
+++ b/test/unit/machine/machine_after_changing_initial_state.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class MachineAfterChangingInitialState < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.initial_state = :idling
+
+    @object = @klass.new
+  end
+
+  def test_should_change_the_initial_state
+    assert_equal :idling, @machine.initial_state(@object).name
+  end
+
+  def test_should_include_in_known_states
+    assert_equal [:parked, :idling], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_reset_original_initial_state
+    refute @machine.state(:parked).initial
+  end
+
+  def test_should_set_new_state_to_initial
+    assert @machine.state(:idling).initial
+  end
+end
+
diff --git a/test/unit/machine/machine_after_changing_owner_class_test.rb b/test/unit/machine/machine_after_changing_owner_class_test.rb
new file mode 100644
index 0000000..47981fb
--- /dev/null
+++ b/test/unit/machine/machine_after_changing_owner_class_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class MachineAfterChangingOwnerClassTest < StateMachinesTest
+  def setup
+    @original_class = Class.new
+    @machine = StateMachines::Machine.new(@original_class)
+
+    @new_class = Class.new(@original_class)
+    @new_machine = @machine.clone
+    @new_machine.owner_class = @new_class
+
+    @object = @new_class.new
+  end
+
+  def test_should_update_owner_class
+    assert_equal @new_class, @new_machine.owner_class
+  end
+
+  def test_should_not_change_original_owner_class
+    assert_equal @original_class, @machine.owner_class
+  end
+
+  def test_should_change_the_associated_machine_in_the_new_class
+    assert_equal @new_machine, @new_class.state_machines[:state]
+  end
+
+  def test_should_not_change_the_associated_machine_in_the_original_class
+    assert_equal @machine, @original_class.state_machines[:state]
+  end
+end
+
diff --git a/test/unit/machine/machine_by_default_test.rb b/test/unit/machine/machine_by_default_test.rb
new file mode 100644
index 0000000..f840001
--- /dev/null
+++ b/test/unit/machine/machine_by_default_test.rb
@@ -0,0 +1,160 @@
+require_relative '../../test_helper'
+
+class MachineByDefaultTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @object = @klass.new
+  end
+
+  def test_should_have_an_owner_class
+    assert_equal @klass, @machine.owner_class
+  end
+
+  def test_should_have_a_name
+    assert_equal :state, @machine.name
+  end
+
+  def test_should_have_an_attribute
+    assert_equal :state, @machine.attribute
+  end
+
+  def test_should_prefix_custom_attributes_with_attribute
+    assert_equal :state_event, @machine.attribute(:event)
+  end
+
+  def test_should_have_an_initial_state
+    refute_nil @machine.initial_state(@object)
+  end
+
+  def test_should_have_a_nil_initial_state
+    assert_nil @machine.initial_state(@object).value
+  end
+
+  def test_should_not_have_any_events
+    refute @machine.events.any?
+  end
+
+  def test_should_not_have_any_before_callbacks
+    assert @machine.callbacks[:before].empty?
+  end
+
+  def test_should_not_have_any_after_callbacks
+    assert @machine.callbacks[:after].empty?
+  end
+
+  def test_should_not_have_any_failure_callbacks
+    assert @machine.callbacks[:failure].empty?
+  end
+
+  def test_should_not_have_an_action
+    assert_nil @machine.action
+  end
+
+  def test_should_use_tranactions
+    assert_equal true, @machine.use_transactions
+  end
+
+  def test_should_not_have_a_namespace
+    assert_nil @machine.namespace
+  end
+
+  def test_should_have_a_nil_state
+    assert_equal [nil], @machine.states.keys
+  end
+
+  def test_should_set_initial_on_nil_state
+    assert @machine.state(nil).initial
+  end
+
+  def test_should_generate_default_messages
+    assert_equal 'is invalid', @machine.generate_message(:invalid)
+    assert_equal 'cannot transition when parked', @machine.generate_message(:invalid_event, [[:state, :parked]])
+    assert_equal 'cannot transition via "park"', @machine.generate_message(:invalid_transition, [[:event, :park]])
+  end
+
+  def test_should_define_a_reader_attribute_for_the_attribute
+    assert @object.respond_to?(:state)
+  end
+
+  def test_should_define_a_writer_attribute_for_the_attribute
+    assert @object.respond_to?(:state=)
+  end
+
+  def test_should_define_a_predicate_for_the_attribute
+    assert @object.respond_to?(:state?)
+  end
+
+  def test_should_define_a_name_reader_for_the_attribute
+    assert @object.respond_to?(:state_name)
+  end
+
+  def test_should_define_an_event_reader_for_the_attribute
+    assert @object.respond_to?(:state_events)
+  end
+
+  def test_should_define_a_transition_reader_for_the_attribute
+    assert @object.respond_to?(:state_transitions)
+  end
+
+  def test_should_define_a_path_reader_for_the_attribute
+    assert @object.respond_to?(:state_paths)
+  end
+
+  def test_should_define_an_event_runner_for_the_attribute
+    assert @object.respond_to?(:fire_state_event)
+  end
+
+  def test_should_not_define_an_event_attribute_reader
+    refute @object.respond_to?(:state_event)
+  end
+
+  def test_should_not_define_an_event_attribute_writer
+    refute @object.respond_to?(:state_event=)
+  end
+
+  def test_should_not_define_an_event_transition_attribute_reader
+    refute @object.respond_to?(:state_event_transition)
+  end
+
+  def test_should_not_define_an_event_transition_attribute_writer
+    refute @object.respond_to?(:state_event_transition=)
+  end
+
+  def test_should_define_a_human_attribute_name_reader_for_the_attribute
+    assert @klass.respond_to?(:human_state_name)
+  end
+
+  def test_should_define_a_human_event_name_reader_for_the_attribute
+    assert @klass.respond_to?(:human_state_event_name)
+  end
+
+  def test_should_not_define_singular_with_scope
+    refute @klass.respond_to?(:with_state)
+  end
+
+  def test_should_not_define_singular_without_scope
+    refute @klass.respond_to?(:without_state)
+  end
+
+  def test_should_not_define_plural_with_scope
+    refute @klass.respond_to?(:with_states)
+  end
+
+  def test_should_not_define_plural_without_scope
+    refute @klass.respond_to?(:without_states)
+  end
+
+  def test_should_extend_owner_class_with_class_methods
+    assert((class << @klass; ancestors; end).include?(StateMachines::ClassMethods))
+  end
+
+  def test_should_include_instance_methods_in_owner_class
+    assert @klass.included_modules.include?(StateMachines::InstanceMethods)
+  end
+
+  def test_should_define_state_machines_reader
+    expected = { state: @machine }
+    assert_equal expected, @klass.state_machines
+  end
+end
diff --git a/test/unit/machine/machine_finder_custom_options_test.rb b/test/unit/machine/machine_finder_custom_options_test.rb
new file mode 100644
index 0000000..181278a
--- /dev/null
+++ b/test/unit/machine/machine_finder_custom_options_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class MachineFinderCustomOptionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.find_or_create(@klass, :status, initial: :parked)
+    @object = @klass.new
+  end
+
+  def test_should_use_custom_attribute
+    assert_equal :status, @machine.attribute
+  end
+
+  def test_should_set_custom_initial_state
+    assert_equal :parked, @machine.initial_state(@object).name
+  end
+end
diff --git a/test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb b/test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb
new file mode 100644
index 0000000..f005113
--- /dev/null
+++ b/test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb
@@ -0,0 +1,85 @@
+require_relative '../../test_helper'
+
+class MachineFinderWithExistingMachineOnSuperclassTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def self.matches?(_klass)
+      false
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(MachineFinderWithExistingMachineOnSuperclassTest::Custom)
+
+    @base_class = Class.new
+    @base_machine = StateMachines::Machine.new(@base_class, :status, action: :save, integration: :custom)
+    @base_machine.event(:ignite) {}
+    @base_machine.before_transition(-> {})
+    @base_machine.after_transition(-> {})
+    @base_machine.around_transition(-> {})
+
+    @klass = Class.new(@base_class)
+    @machine = StateMachines::Machine.find_or_create(@klass, :status) {}
+  end
+
+  def test_should_accept_a_block
+    called = false
+    StateMachines::Machine.find_or_create(Class.new(@base_class)) do
+      called = respond_to?(:event)
+    end
+
+    assert called
+  end
+
+  def test_should_not_create_a_new_machine_if_no_block_or_options
+    machine = StateMachines::Machine.find_or_create(Class.new(@base_class), :status)
+
+    assert_same machine, @base_machine
+  end
+
+  def test_should_create_a_new_machine_if_given_options
+    machine = StateMachines::Machine.find_or_create(@klass, :status, initial: :parked)
+
+    refute_nil machine
+    refute_same machine, @base_machine
+  end
+
+  def test_should_create_a_new_machine_if_given_block
+    refute_nil @machine
+    refute_same @machine, @base_machine
+  end
+
+  def test_should_copy_the_base_attribute
+    assert_equal :status, @machine.attribute
+  end
+
+  def test_should_copy_the_base_configuration
+    assert_equal :save, @machine.action
+  end
+
+  def test_should_copy_events
+    # Can't assert equal arrays since their machines change
+    assert_equal 1, @machine.events.length
+  end
+
+  def test_should_copy_before_callbacks
+    assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before]
+  end
+
+  def test_should_copy_after_transitions
+    assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after]
+  end
+
+  def test_should_use_the_same_integration
+    class_ancestors = class << @machine
+      ancestors
+    end
+
+    assert(class_ancestors.include?(MachineFinderWithExistingMachineOnSuperclassTest::Custom))
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/machine/machine_finder_with_existing_on_same_class_test.rb b/test/unit/machine/machine_finder_with_existing_on_same_class_test.rb
new file mode 100644
index 0000000..6390e10
--- /dev/null
+++ b/test/unit/machine/machine_finder_with_existing_on_same_class_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class MachineFinderWithExistingOnSameClassTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @existing_machine = StateMachines::Machine.new(@klass)
+    @machine = StateMachines::Machine.find_or_create(@klass)
+  end
+
+  def test_should_accept_a_block
+    called = false
+    StateMachines::Machine.find_or_create(@klass) do
+      called = respond_to?(:event)
+    end
+
+    assert called
+  end
+
+  def test_should_not_create_a_new_machine
+    assert_same @machine, @existing_machine
+  end
+end
+
diff --git a/test/unit/machine/machine_finder_without_existing_machine_test.rb b/test/unit/machine/machine_finder_without_existing_machine_test.rb
new file mode 100644
index 0000000..24cf23e
--- /dev/null
+++ b/test/unit/machine/machine_finder_without_existing_machine_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class MachineFinderWithoutExistingMachineTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.find_or_create(@klass)
+  end
+
+  def test_should_accept_a_block
+    called = false
+    StateMachines::Machine.find_or_create(Class.new) do
+      called = respond_to?(:event)
+    end
+
+    assert called
+  end
+
+  def test_should_create_a_new_machine
+    refute_nil @machine
+  end
+
+  def test_should_use_default_state
+    assert_equal :state, @machine.attribute
+  end
+end
diff --git a/test/unit/machine/machine_persistence_test.rb b/test/unit/machine/machine_persistence_test.rb
new file mode 100644
index 0000000..d7c05e8
--- /dev/null
+++ b/test/unit/machine/machine_persistence_test.rb
@@ -0,0 +1,52 @@
+require_relative '../../test_helper'
+
+class MachinePersistenceTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :state_event
+    end
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @object = @klass.new
+  end
+
+  def test_should_allow_reading_state
+    assert_equal 'parked', @machine.read(@object, :state)
+  end
+
+  def test_should_allow_reading_custom_attributes
+    assert_nil @machine.read(@object, :event)
+
+    @object.state_event = 'ignite'
+    assert_equal 'ignite', @machine.read(@object, :event)
+  end
+
+  def test_should_allow_reading_custom_instance_variables
+    @klass.class_eval do
+      attr_writer :state_value
+    end
+
+    @object.state_value = 1
+    assert_raises(NoMethodError) { @machine.read(@object, :value) }
+    assert_equal 1, @machine.read(@object, :value, true)
+  end
+
+  def test_should_allow_writing_state
+    @machine.write(@object, :state, 'idling')
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_allow_writing_custom_attributes
+    @machine.write(@object, :event, 'ignite')
+    assert_equal 'ignite', @object.state_event
+  end
+
+  def test_should_allow_writing_custom_instance_variables
+    @klass.class_eval do
+      attr_reader :state_value
+    end
+
+    assert_raises(NoMethodError) { @machine.write(@object, :value, 1) }
+    assert_equal 1, @machine.write(@object, :value, 1, true)
+    assert_equal 1, @object.state_value
+  end
+end
diff --git a/test/unit/machine/machine_state_initialization_test.rb b/test/unit/machine/machine_state_initialization_test.rb
new file mode 100644
index 0000000..1a642a8
--- /dev/null
+++ b/test/unit/machine/machine_state_initialization_test.rb
@@ -0,0 +1,56 @@
+require_relative '../../test_helper'
+
+class MachineStateInitializationTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, initialize: false)
+
+    @object = @klass.new
+    @object.state = nil
+  end
+
+  def test_should_set_states_if_nil
+    @machine.initialize_state(@object)
+
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_set_states_if_empty
+    @object.state = ''
+    @machine.initialize_state(@object)
+
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_set_states_if_not_empty
+    @object.state = 'idling'
+    @machine.initialize_state(@object)
+
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_set_states_if_not_empty_and_forced
+    @object.state = 'idling'
+    @machine.initialize_state(@object, force: true)
+
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_set_state_if_nil_and_nil_is_valid_state
+    @machine.state :initial, value: nil
+    @machine.initialize_state(@object)
+
+    assert_nil @object.state
+  end
+
+  def test_should_write_to_hash_if_specified
+    @machine.initialize_state(@object, to: hash = {})
+    assert_equal({ 'state' => 'parked' }, hash)
+  end
+
+  def test_should_not_write_to_object_if_writing_to_hash
+    @machine.initialize_state(@object, to: {})
+    assert_nil @object.state
+  end
+end
+
diff --git a/test/unit/machine/machine_test.rb b/test/unit/machine/machine_test.rb
new file mode 100644
index 0000000..44718a1
--- /dev/null
+++ b/test/unit/machine/machine_test.rb
@@ -0,0 +1,30 @@
+require_relative '../../test_helper'
+
+class MachineTest < StateMachinesTest
+  def test_should_raise_exception_if_invalid_option_specified
+    assert_raises(ArgumentError) { StateMachines::Machine.new(Class.new, invalid: true) }
+  end
+
+  def test_should_not_raise_exception_if_custom_messages_specified
+    StateMachines::Machine.new(Class.new, messages: { invalid_transition: 'custom' })
+  end
+
+  def test_should_evaluate_a_block_during_initialization
+    called = true
+    StateMachines::Machine.new(Class.new) do
+      called = respond_to?(:event)
+    end
+
+    assert called
+  end
+
+  def test_should_provide_matcher_helpers_during_initialization
+    matchers = []
+
+    StateMachines::Machine.new(Class.new) do
+      matchers = [all, any, same]
+    end
+
+    assert_equal [StateMachines::AllMatcher.instance, StateMachines::AllMatcher.instance, StateMachines::LoopbackMatcher.instance], matchers
+  end
+end
diff --git a/test/unit/machine/machine_with_action_already_overridden_test.rb b/test/unit/machine/machine_with_action_already_overridden_test.rb
new file mode 100644
index 0000000..0efcf8c
--- /dev/null
+++ b/test/unit/machine/machine_with_action_already_overridden_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class MachineWithActionAlreadyOverriddenTest < StateMachinesTest
+  def setup
+    @superclass = Class.new do
+      def save
+      end
+    end
+    @klass = Class.new(@superclass)
+
+    StateMachines::Machine.new(@klass, action: :save)
+    @machine = StateMachines::Machine.new(@klass, :status, action: :save)
+    @object = @klass.new
+  end
+
+  def test_should_not_redefine_action
+    assert_equal 1, @klass.ancestors.select { |ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save) }.length
+  end
+
+  def test_should_mark_action_hook_as_defined
+    assert @machine.action_hook?
+  end
+end
diff --git a/test/unit/machine/machine_with_action_defined_in_class_test.rb b/test/unit/machine/machine_with_action_defined_in_class_test.rb
new file mode 100644
index 0000000..44f0bee
--- /dev/null
+++ b/test/unit/machine/machine_with_action_defined_in_class_test.rb
@@ -0,0 +1,37 @@
+require_relative '../../test_helper'
+
+class MachineWithActionDefinedInClassTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @object = @klass.new
+  end
+
+  def test_should_define_an_event_attribute_reader
+    assert @object.respond_to?(:state_event)
+  end
+
+  def test_should_define_an_event_attribute_writer
+    assert @object.respond_to?(:state_event=)
+  end
+
+  def test_should_define_an_event_transition_attribute_reader
+    assert @object.respond_to?(:state_event_transition, true)
+  end
+
+  def test_should_define_an_event_transition_attribute_writer
+    assert @object.respond_to?(:state_event_transition=, true)
+  end
+
+  def test_should_not_define_action
+    refute @klass.ancestors.any? { |ancestor| ancestor != @klass && ancestor.method_defined?(:save) }
+  end
+
+  def test_should_not_mark_action_hook_as_defined
+    refute @machine.action_hook?
+  end
+end
diff --git a/test/unit/machine/machine_with_action_defined_in_included_module_test.rb b/test/unit/machine/machine_with_action_defined_in_included_module_test.rb
new file mode 100644
index 0000000..cd46a5b
--- /dev/null
+++ b/test/unit/machine/machine_with_action_defined_in_included_module_test.rb
@@ -0,0 +1,46 @@
+require_relative '../../test_helper'
+
+class MachineWithActionDefinedInIncludedModuleTest < StateMachinesTest
+  def setup
+    @mod = mod = Module.new do
+      def save
+      end
+    end
+
+    @klass = Class.new do
+      include mod
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @object = @klass.new
+  end
+
+  def test_should_define_an_event_attribute_reader
+    assert @object.respond_to?(:state_event)
+  end
+
+  def test_should_define_an_event_attribute_writer
+    assert @object.respond_to?(:state_event=)
+  end
+
+  def test_should_define_an_event_transition_attribute_reader
+    assert @object.respond_to?(:state_event_transition, true)
+  end
+
+  def test_should_define_an_event_transition_attribute_writer
+    assert @object.respond_to?(:state_event_transition=, true)
+  end
+
+  def test_should_define_action
+    assert @klass.ancestors.any? { |ancestor| ![@klass, @mod].include?(ancestor) && ancestor.method_defined?(:save) }
+  end
+
+  def test_should_keep_action_public
+    assert @klass.public_method_defined?(:save)
+  end
+
+  def test_should_mark_action_hook_as_defined
+    assert @machine.action_hook?
+  end
+end
+
diff --git a/test/unit/machine/machine_with_action_defined_in_superclass_test.rb b/test/unit/machine/machine_with_action_defined_in_superclass_test.rb
new file mode 100644
index 0000000..007ae7f
--- /dev/null
+++ b/test/unit/machine/machine_with_action_defined_in_superclass_test.rb
@@ -0,0 +1,43 @@
+require_relative '../../test_helper'
+
+class MachineWithActionDefinedInSuperclassTest < StateMachinesTest
+  def setup
+    @superclass = Class.new do
+      def save
+      end
+    end
+    @klass = Class.new(@superclass)
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @object = @klass.new
+  end
+
+  def test_should_define_an_event_attribute_reader
+    assert @object.respond_to?(:state_event)
+  end
+
+  def test_should_define_an_event_attribute_writer
+    assert @object.respond_to?(:state_event=)
+  end
+
+  def test_should_define_an_event_transition_attribute_reader
+    assert @object.respond_to?(:state_event_transition, true)
+  end
+
+  def test_should_define_an_event_transition_attribute_writer
+    assert @object.respond_to?(:state_event_transition=, true)
+  end
+
+  def test_should_define_action
+    assert @klass.ancestors.any? { |ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save) }
+  end
+
+  def test_should_keep_action_public
+    assert @klass.public_method_defined?(:save)
+  end
+
+  def test_should_mark_action_hook_as_defined
+    assert @machine.action_hook?
+  end
+end
+
diff --git a/test/unit/machine/machine_with_action_undefined_test.rb b/test/unit/machine/machine_with_action_undefined_test.rb
new file mode 100644
index 0000000..4239ed8
--- /dev/null
+++ b/test/unit/machine/machine_with_action_undefined_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class MachineWithActionUndefinedTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @object = @klass.new
+  end
+
+  def test_should_define_an_event_attribute_reader
+    assert @object.respond_to?(:state_event)
+  end
+
+  def test_should_define_an_event_attribute_writer
+    assert @object.respond_to?(:state_event=)
+  end
+
+  def test_should_define_an_event_transition_attribute_reader
+    assert @object.respond_to?(:state_event_transition, true)
+  end
+
+  def test_should_define_an_event_transition_attribute_writer
+    assert @object.respond_to?(:state_event_transition=, true)
+  end
+
+  def test_should_not_define_action
+    refute @object.respond_to?(:save)
+  end
+
+  def test_should_not_mark_action_hook_as_defined
+    refute @machine.action_hook?
+  end
+end
diff --git a/test/unit/machine/machine_with_cached_state_test.rb b/test/unit/machine/machine_with_cached_state_test.rb
new file mode 100644
index 0000000..14ad32a
--- /dev/null
+++ b/test/unit/machine/machine_with_cached_state_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class MachineWithCachedStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @state = @machine.state :parked, value: -> { Object.new }, cache: true
+
+    @object = @klass.new
+  end
+
+  def test_should_use_evaluated_value
+    assert_instance_of Object, @object.state
+  end
+
+  def test_use_same_value_across_multiple_objects
+    assert_equal @object.state, @klass.new.state
+  end
+end
+
diff --git a/test/unit/machine/machine_with_class_helpers_test.rb b/test/unit/machine/machine_with_class_helpers_test.rb
new file mode 100644
index 0000000..5d04798
--- /dev/null
+++ b/test/unit/machine/machine_with_class_helpers_test.rb
@@ -0,0 +1,179 @@
+require_relative '../../test_helper'
+require 'stringio'
+
+class MachineWithClassHelpersTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+  end
+
+  def test_should_not_redefine_existing_public_methods
+    class << @klass
+      def states
+        []
+      end
+    end
+
+    @machine.define_helper(:class, :states) {}
+    assert_equal [], @klass.states
+  end
+
+  def test_should_not_redefine_existing_protected_methods
+    class << @klass
+      protected
+      def states
+        []
+      end
+    end
+
+    @machine.define_helper(:class, :states) {}
+    assert_equal [], @klass.send(:states)
+  end
+
+  def test_should_not_redefine_existing_private_methods
+    class << @klass
+      private
+      def states
+        []
+      end
+    end
+
+    @machine.define_helper(:class, :states) {}
+    assert_equal [], @klass.send(:states)
+  end
+
+  def test_should_warn_if_defined_in_superclass
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    superclass = Class.new do
+      def self.park
+      end
+    end
+    klass = Class.new(superclass)
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:class, :park) {}
+    assert_equal "Class method \"park\" is already defined in #{superclass}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_warn_if_defined_in_multiple_superclasses
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    superclass1 = Class.new do
+      def self.park
+      end
+    end
+    superclass2 = Class.new(superclass1) do
+      def self.park
+      end
+    end
+    klass = Class.new(superclass2)
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:class, :park) {}
+    assert_equal "Class method \"park\" is already defined in #{superclass1}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_warn_if_defined_in_module_prior_to_helper_module
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    mod = Module.new do
+      def park
+      end
+    end
+    klass = Class.new do
+      extend mod
+    end
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:class, :park) {}
+    assert_equal "Class method \"park\" is already defined in #{mod}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_not_warn_if_defined_in_module_after_helper_module
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    klass = Class.new
+    machine = StateMachines::Machine.new(klass)
+
+    mod = Module.new do
+      def park
+      end
+    end
+    klass.class_eval do
+      extend mod
+    end
+
+    machine.define_helper(:class, :park) {}
+    assert_equal '', $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
+    @original_stderr, $stderr = $stderr, StringIO.new
+    StateMachines::Machine.ignore_method_conflicts = true
+
+    superclass = Class.new do
+      def self.park
+      end
+    end
+    klass = Class.new(superclass)
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:class, :park) { true }
+    assert_equal '', $stderr.string
+    assert_equal true, klass.park
+  ensure
+    StateMachines::Machine.ignore_method_conflicts = false
+    $stderr = @original_stderr
+  end
+
+  def test_should_define_nonexistent_methods
+    @machine.define_helper(:class, :states) { [] }
+    assert_equal [], @klass.states
+  end
+
+  def test_should_warn_if_defined_multiple_times
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @machine.define_helper(:class, :states) {}
+    @machine.define_helper(:class, :states) {}
+
+    assert_equal "Class method \"states\" is already defined in #{@klass} :state class helpers, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_pass_context_as_arguments
+    helper_args = nil
+    @machine.define_helper(:class, :states) { |*args| helper_args = args }
+    @klass.states
+    assert_equal 2, helper_args.length
+    assert_equal [@machine, @klass], helper_args
+  end
+
+  def test_should_pass_method_arguments_through
+    helper_args = nil
+    @machine.define_helper(:class, :states) { |*args| helper_args = args }
+    @klass.states(1, 2, 3)
+    assert_equal 5, helper_args.length
+    assert_equal [@machine, @klass, 1, 2, 3], helper_args
+  end
+
+  def test_should_allow_string_evaluation
+    @machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
+      def states
+        []
+      end
+    end_eval
+    assert_equal [], @klass.states
+  end
+end
+
diff --git a/test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb b/test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb
new file mode 100644
index 0000000..fd09267
--- /dev/null
+++ b/test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb
@@ -0,0 +1,244 @@
+require_relative '../../test_helper'
+
+class MachineWithConflictingHelpersAfterDefinitionTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def create_with_scope(_name)
+      ->(_klass, _values) { [] }
+    end
+
+    def create_without_scope(_name)
+      ->(_klass, _values) { [] }
+    end
+  end
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+    StateMachines::Integrations.register(MachineWithConflictingHelpersAfterDefinitionTest::Custom)
+    @klass = Class.new do
+      def self.with_state
+        :with_state
+      end
+
+      def self.with_states
+        :with_states
+      end
+
+      def self.without_state
+        :without_state
+      end
+
+      def self.without_states
+        :without_states
+      end
+
+      def self.human_state_name
+        :human_state_name
+      end
+
+      def self.human_state_event_name
+        :human_state_event_name
+      end
+
+      attr_accessor :status
+
+      def state
+        'parked'
+      end
+
+      def state=(value)
+        self.status = value
+      end
+
+      def state?
+        true
+      end
+
+      def state_name
+        :parked
+      end
+
+      def human_state_name
+        'parked'
+      end
+
+      def state_events
+        [:ignite]
+      end
+
+      def state_transitions
+        [{ parked: :idling }]
+      end
+
+      def state_paths
+        [[{ parked: :idling }]]
+      end
+
+      def fire_state_event
+        true
+      end
+    end
+
+
+
+    @machine = StateMachines::Machine.new(@klass, integration: :custom)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+    @object = @klass.new
+  end
+
+  def test_should_not_redefine_singular_with_scope
+    assert_equal :with_state, @klass.with_state
+  end
+
+  def test_should_not_redefine_plural_with_scope
+    assert_equal :with_states, @klass.with_states
+  end
+
+  def test_should_not_redefine_singular_without_scope
+    assert_equal :without_state, @klass.without_state
+  end
+
+  def test_should_not_redefine_plural_without_scope
+    assert_equal :without_states, @klass.without_states
+  end
+
+  def test_should_not_redefine_human_attribute_name_reader
+    assert_equal :human_state_name, @klass.human_state_name
+  end
+
+  def test_should_not_redefine_human_event_name_reader
+    assert_equal :human_state_event_name, @klass.human_state_event_name
+  end
+
+  def test_should_not_redefine_attribute_reader
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_redefine_attribute_writer
+    @object.state = 'parked'
+    assert_equal 'parked', @object.status
+  end
+
+  def test_should_not_define_attribute_predicate
+    assert @object.state?
+  end
+
+  def test_should_not_redefine_attribute_name_reader
+    assert_equal :parked, @object.state_name
+  end
+
+  def test_should_not_redefine_attribute_human_name_reader
+    assert_equal 'parked', @object.human_state_name
+  end
+
+  def test_should_not_redefine_attribute_events_reader
+    assert_equal [:ignite], @object.state_events
+  end
+
+  def test_should_not_redefine_attribute_transitions_reader
+    assert_equal [{ parked: :idling }], @object.state_transitions
+  end
+
+  def test_should_not_redefine_attribute_paths_reader
+    assert_equal [[{ parked: :idling }]], @object.state_paths
+  end
+
+  def test_should_not_redefine_event_runner
+    assert_equal true, @object.fire_state_event
+  end
+
+  def test_should_allow_super_chaining
+    @klass.class_eval do
+      def self.with_state(*states)
+        super
+      end
+
+      def self.with_states(*states)
+        super
+      end
+
+      def self.without_state(*states)
+        super
+      end
+
+      def self.without_states(*states)
+        super
+      end
+
+      def self.human_state_name(state)
+        super
+      end
+
+      def self.human_state_event_name(event)
+        super
+      end
+
+      attr_accessor :status
+
+      def state
+        super
+      end
+
+      def state=(value)
+        super
+      end
+
+      def state?(state)
+        super
+      end
+
+      def state_name
+        super
+      end
+
+      def human_state_name
+        super
+      end
+
+      def state_events
+        super
+      end
+
+      def state_transitions
+        super
+      end
+
+      def state_paths
+        super
+      end
+
+      def fire_state_event(event)
+        super
+      end
+    end
+
+    assert_equal [], @klass.with_state
+    assert_equal [], @klass.with_states
+    assert_equal [], @klass.without_state
+    assert_equal [], @klass.without_states
+    assert_equal 'parked', @klass.human_state_name(:parked)
+    assert_equal 'ignite', @klass.human_state_event_name(:ignite)
+
+    assert_equal nil, @object.state
+    @object.state = 'idling'
+    assert_equal 'idling', @object.state
+    assert_equal nil, @object.status
+    assert_equal false, @object.state?(:parked)
+    assert_equal :idling, @object.state_name
+    assert_equal 'idling', @object.human_state_name
+    assert_equal [], @object.state_events
+    assert_equal [], @object.state_transitions
+    assert_equal [], @object.state_paths
+    assert_equal false, @object.fire_state_event(:ignite)
+  end
+
+  def test_should_not_output_warning
+    assert_equal '', $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb b/test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb
new file mode 100644
index 0000000..e242958
--- /dev/null
+++ b/test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb
@@ -0,0 +1,175 @@
+require_relative '../../test_helper'
+
+class MachineWithConflictingHelpersBeforeDefinitionTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def create_with_scope(_name)
+      lambda { |_klass, _values| [] }
+    end
+
+    def create_without_scope(_name)
+      lambda { |_klass, _values| [] }
+    end
+  end
+
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    StateMachines::Integrations.register(MachineWithConflictingHelpersBeforeDefinitionTest::Custom)
+
+    @superclass = Class.new do
+      def self.with_state
+        :with_state
+      end
+
+      def self.with_states
+        :with_states
+      end
+
+      def self.without_state
+        :without_state
+      end
+
+      def self.without_states
+        :without_states
+      end
+
+      def self.human_state_name
+        :human_state_name
+      end
+
+      def self.human_state_event_name
+        :human_state_event_name
+      end
+
+      attr_accessor :status
+
+      def state
+        'parked'
+      end
+
+      def state=(value)
+        self.status = value
+      end
+
+      def state?
+        true
+      end
+
+      def state_name
+        :parked
+      end
+
+      def human_state_name
+        'parked'
+      end
+
+      def state_events
+        [:ignite]
+      end
+
+      def state_transitions
+        [{ parked: :idling }]
+      end
+
+      def state_paths
+        [[{ parked: :idling }]]
+      end
+
+      def fire_state_event
+        true
+      end
+    end
+    @klass = Class.new(@superclass)
+    @machine = StateMachines::Machine.new(@klass, integration: :custom)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+    @object = @klass.new
+  end
+
+  def test_should_not_redefine_singular_with_scope
+    assert_equal :with_state, @klass.with_state
+  end
+
+  def test_should_not_redefine_plural_with_scope
+    assert_equal :with_states, @klass.with_states
+  end
+
+  def test_should_not_redefine_singular_without_scope
+    assert_equal :without_state, @klass.without_state
+  end
+
+  def test_should_not_redefine_plural_without_scope
+    assert_equal :without_states, @klass.without_states
+  end
+
+  def test_should_not_redefine_human_attribute_name_reader
+    assert_equal :human_state_name, @klass.human_state_name
+  end
+
+  def test_should_not_redefine_human_event_name_reader
+    assert_equal :human_state_event_name, @klass.human_state_event_name
+  end
+
+  def test_should_not_redefine_attribute_reader
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_redefine_attribute_writer
+    @object.state = 'parked'
+    assert_equal 'parked', @object.status
+  end
+
+  def test_should_not_define_attribute_predicate
+    assert @object.state?
+  end
+
+  def test_should_not_redefine_attribute_name_reader
+    assert_equal :parked, @object.state_name
+  end
+
+  def test_should_not_redefine_attribute_human_name_reader
+    assert_equal 'parked', @object.human_state_name
+  end
+
+  def test_should_not_redefine_attribute_events_reader
+    assert_equal [:ignite], @object.state_events
+  end
+
+  def test_should_not_redefine_attribute_transitions_reader
+    assert_equal [{ parked: :idling }], @object.state_transitions
+  end
+
+  def test_should_not_redefine_attribute_paths_reader
+    assert_equal [[{ parked: :idling }]], @object.state_paths
+  end
+
+  def test_should_not_redefine_event_runner
+    assert_equal true, @object.fire_state_event
+  end
+
+  def test_should_output_warning
+    expected = [
+        'Instance method "state_events"',
+        'Instance method "state_transitions"',
+        'Instance method "fire_state_event"',
+        'Instance method "state_paths"',
+        'Class method "human_state_name"',
+        'Class method "human_state_event_name"',
+        'Instance method "state_name"',
+        'Instance method "human_state_name"',
+        'Class method "with_state"',
+        'Class method "with_states"',
+        'Class method "without_state"',
+        'Class method "without_states"'
+    ].map { |method| "#{method} is already defined in #{@superclass}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n" }.join
+
+    assert_equal expected, $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
+
diff --git a/test/unit/machine/machine_with_custom_action_test.rb b/test/unit/machine/machine_with_custom_action_test.rb
new file mode 100644
index 0000000..2252d47
--- /dev/null
+++ b/test/unit/machine/machine_with_custom_action_test.rb
@@ -0,0 +1,11 @@
+require_relative '../../test_helper'
+
+class MachineWithCustomActionTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new, action: :save)
+  end
+
+  def test_should_use_the_custom_action
+    assert_equal :save, @machine.action
+  end
+end
diff --git a/test/unit/machine/machine_with_custom_attribute_test.rb b/test/unit/machine/machine_with_custom_attribute_test.rb
new file mode 100644
index 0000000..5acef98
--- /dev/null
+++ b/test/unit/machine/machine_with_custom_attribute_test.rb
@@ -0,0 +1,103 @@
+require_relative '../../test_helper'
+
+module MachineWithCustomAttributeIntegration
+  include StateMachines::Integrations::Base
+
+  def self.integration_name
+    :custom_attribute
+  end
+
+  @defaults = { action: :save, use_transactions: false }
+
+  def create_with_scope(_name)
+    -> {}
+  end
+
+  def create_without_scope(_name)
+    -> {}
+  end
+end
+
+class MachineWithCustomAttributeTest < StateMachinesTest
+  def setup
+    StateMachines::Integrations.register(MachineWithCustomAttributeIntegration)
+
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, :state, attribute: :state_id, initial: :active, integration: :custom_attribute) do
+      event :ignite do
+        transition parked: :idling
+      end
+    end
+    @object = @klass.new
+  end
+
+  def test_should_define_a_reader_attribute_for_the_attribute
+    assert @object.respond_to?(:state_id)
+  end
+
+  def test_should_define_a_writer_attribute_for_the_attribute
+    assert @object.respond_to?(:state_id=)
+  end
+
+  def test_should_define_a_predicate_for_the_attribute
+    assert @object.respond_to?(:state?)
+  end
+
+  def test_should_define_a_name_reader_for_the_attribute
+    assert @object.respond_to?(:state_name)
+  end
+
+  def test_should_define_a_human_name_reader_for_the_attribute
+    assert @object.respond_to?(:state_name)
+  end
+
+  def test_should_define_an_event_reader_for_the_attribute
+    assert @object.respond_to?(:state_events)
+  end
+
+  def test_should_define_a_transition_reader_for_the_attribute
+    assert @object.respond_to?(:state_transitions)
+  end
+
+  def test_should_define_a_path_reader_for_the_attribute
+    assert @object.respond_to?(:state_paths)
+  end
+
+  def test_should_define_an_event_runner_for_the_attribute
+    assert @object.respond_to?(:fire_state_event)
+  end
+
+  def test_should_define_a_human_attribute_name_reader
+    assert @klass.respond_to?(:human_state_name)
+  end
+
+  def test_should_define_a_human_event_name_reader
+    assert @klass.respond_to?(:human_state_event_name)
+  end
+
+  def test_should_define_singular_with_scope
+    assert @klass.respond_to?(:with_state)
+  end
+
+  def test_should_define_singular_without_scope
+    assert @klass.respond_to?(:without_state)
+  end
+
+  def test_should_define_plural_with_scope
+    assert @klass.respond_to?(:with_states)
+  end
+
+  def test_should_define_plural_without_scope
+    assert @klass.respond_to?(:without_states)
+  end
+
+  def test_should_define_state_machines_reader
+    expected = { state: @machine }
+    assert_equal expected, @klass.state_machines
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
+
diff --git a/test/unit/machine/machine_with_custom_initialize_test.rb b/test/unit/machine/machine_with_custom_initialize_test.rb
new file mode 100644
index 0000000..54dc9dd
--- /dev/null
+++ b/test/unit/machine/machine_with_custom_initialize_test.rb
@@ -0,0 +1,24 @@
+require_relative '../../test_helper'
+
+class MachineWithCustomInitializeTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def initialize(state = nil, options = {})
+        @state = state
+        initialize_state_machines(options)
+      end
+    end
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @object = @klass.new
+  end
+
+  def test_should_initialize_state
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_allow_custom_options
+    @machine.state :idling
+    @object = @klass.new('idling', static: :force)
+    assert_equal 'parked', @object.state
+  end
+end
diff --git a/test/unit/machine/machine_with_custom_integration_test.rb b/test/unit/machine/machine_with_custom_integration_test.rb
new file mode 100644
index 0000000..da319dd
--- /dev/null
+++ b/test/unit/machine/machine_with_custom_integration_test.rb
@@ -0,0 +1,72 @@
+require_relative '../../test_helper'
+require_relative '../../../test/files/models/vehicle'
+
+class MachineWithCustomIntegrationTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def self.matching_ancestors
+      ['Vehicle']
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(MachineWithCustomIntegrationTest::Custom)
+
+    @klass = Vehicle
+  end
+
+  def test_should_be_extended_by_the_integration_if_explicit
+    machine = StateMachines::Machine.new(@klass, integration: :custom)
+    assert((class << machine; ancestors; end).include?(MachineWithCustomIntegrationTest::Custom))
+  end
+
+  def test_should_not_be_extended_by_the_integration_if_implicit_but_not_available
+    MachineWithCustomIntegrationTest::Custom.class_eval do
+      class << self; remove_method :matching_ancestors; end
+      def self.matching_ancestors
+        []
+      end
+    end
+
+    machine = StateMachines::Machine.new(@klass)
+    assert(!(class << machine; ancestors; end).include?(MachineWithCustomIntegrationTest::Custom))
+  end
+
+  def test_should_not_be_extended_by_the_integration_if_implicit_but_not_matched
+    MachineWithCustomIntegrationTest::Custom.class_eval do
+      class << self; remove_method :matching_ancestors; end
+      def self.matching_ancestors
+        []
+      end
+    end
+
+    machine = StateMachines::Machine.new(@klass)
+    assert(!(class << machine; ancestors; end).include?(MachineWithCustomIntegrationTest::Custom))
+  end
+
+  def test_should_be_extended_by_the_integration_if_implicit_and_available_and_matches
+    machine = StateMachines::Machine.new(@klass)
+    assert((class << machine; ancestors; end).include?(MachineWithCustomIntegrationTest::Custom))
+  end
+
+  def test_should_not_be_extended_by_the_integration_if_nil
+    machine = StateMachines::Machine.new(@klass, integration: nil)
+    assert(!(class << machine; ancestors; end).include?(MachineWithCustomIntegrationTest::Custom))
+  end
+
+  def test_should_not_be_extended_by_the_integration_if_false
+    machine = StateMachines::Machine.new(@klass, integration: false)
+    assert(!(class << machine; ancestors; end).include?(MachineWithCustomIntegrationTest::Custom))
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+    MachineWithCustomIntegrationTest::Custom.class_eval do
+      class << self; remove_method :matching_ancestors; end
+      def self.matching_ancestors
+        ['Vehicle']
+      end
+    end
+  end
+end
diff --git a/test/unit/machine/machine_with_custom_invalidation_test.rb b/test/unit/machine/machine_with_custom_invalidation_test.rb
new file mode 100644
index 0000000..fe7371d
--- /dev/null
+++ b/test/unit/machine/machine_with_custom_invalidation_test.rb
@@ -0,0 +1,39 @@
+require_relative '../../test_helper'
+
+class MachineWithCustomInvalidationTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def invalidate(object, _attribute, message, values = [])
+      object.error = generate_message(message, values)
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(MachineWithCustomInvalidationTest::Custom)
+
+    @klass = Class.new do
+      attr_accessor :error
+    end
+
+    @machine = StateMachines::Machine.new(@klass, integration: :custom, messages: {invalid_transition: 'cannot %s'})
+    @machine.state :parked
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_generate_custom_message
+    assert_equal 'cannot park', @machine.generate_message(:invalid_transition, [[:event, :park]])
+  end
+
+  def test_use_custom_message
+    @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
+    assert_equal 'cannot park', @object.error
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
+
diff --git a/test/unit/machine/machine_with_custom_name_test.rb b/test/unit/machine/machine_with_custom_name_test.rb
new file mode 100644
index 0000000..402fdf8
--- /dev/null
+++ b/test/unit/machine/machine_with_custom_name_test.rb
@@ -0,0 +1,57 @@
+require_relative '../../test_helper'
+
+class MachineWithCustomNameTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, :status)
+    @object = @klass.new
+  end
+
+  def test_should_use_custom_name
+    assert_equal :status, @machine.name
+  end
+
+  def test_should_use_custom_name_for_attribute
+    assert_equal :status, @machine.attribute
+  end
+
+  def test_should_prefix_custom_attributes_with_custom_name
+    assert_equal :status_event, @machine.attribute(:event)
+  end
+
+  def test_should_define_a_reader_attribute_for_the_attribute
+    assert @object.respond_to?(:status)
+  end
+
+  def test_should_define_a_writer_attribute_for_the_attribute
+    assert @object.respond_to?(:status=)
+  end
+
+  def test_should_define_a_predicate_for_the_attribute
+    assert @object.respond_to?(:status?)
+  end
+
+  def test_should_define_a_name_reader_for_the_attribute
+    assert @object.respond_to?(:status_name)
+  end
+
+  def test_should_define_an_event_reader_for_the_attribute
+    assert @object.respond_to?(:status_events)
+  end
+
+  def test_should_define_a_transition_reader_for_the_attribute
+    assert @object.respond_to?(:status_transitions)
+  end
+
+  def test_should_define_an_event_runner_for_the_attribute
+    assert @object.respond_to?(:fire_status_event)
+  end
+
+  def test_should_define_a_human_attribute_name_reader_for_the_attribute
+    assert @klass.respond_to?(:human_status_name)
+  end
+
+  def test_should_define_a_human_event_name_reader_for_the_attribute
+    assert @klass.respond_to?(:human_status_event_name)
+  end
+end
diff --git a/test/unit/machine/machine_with_custom_plural_test.rb b/test/unit/machine/machine_with_custom_plural_test.rb
new file mode 100644
index 0000000..c42388b
--- /dev/null
+++ b/test/unit/machine/machine_with_custom_plural_test.rb
@@ -0,0 +1,52 @@
+require_relative '../../test_helper'
+
+class MachineWithCustomPluralTest < StateMachinesTest
+  def setup
+    @integration = Module.new do
+      include StateMachines::Integrations::Base
+
+      class << self; attr_accessor :with_scopes, :without_scopes; end
+      @with_scopes = []
+      @without_scopes = []
+
+      def create_with_scope(name)
+        MachineWithCustomPluralTest::Custom.with_scopes << name
+        lambda {}
+      end
+
+      def create_without_scope(name)
+        MachineWithCustomPluralTest::Custom.without_scopes << name
+        lambda {}
+      end
+    end
+
+    MachineWithCustomPluralTest.const_set('Custom', @integration)
+    StateMachines::Integrations.register(MachineWithCustomPluralTest::Custom)
+  end
+
+  def test_should_define_a_singular_and_plural_with_scope
+    StateMachines::Machine.new(Class.new, integration: :custom, plural: 'staties')
+    assert_equal %w(with_state with_staties), @integration.with_scopes
+  end
+
+  def test_should_define_a_singular_and_plural_without_scope
+    StateMachines::Machine.new(Class.new, integration: :custom, plural: 'staties')
+    assert_equal %w(without_state without_staties), @integration.without_scopes
+  end
+
+  def test_should_define_single_with_scope_if_singular_same_as_plural
+    StateMachines::Machine.new(Class.new, integration: :custom, plural: 'state')
+    assert_equal %w(with_state), @integration.with_scopes
+  end
+
+  def test_should_define_single_without_scope_if_singular_same_as_plural
+    StateMachines::Machine.new(Class.new, integration: :custom, plural: 'state')
+    assert_equal %w(without_state), @integration.without_scopes
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+    MachineWithCustomPluralTest.send(:remove_const, 'Custom')
+  end
+end
+
diff --git a/test/unit/machine/machine_with_dynamic_initial_state_test.rb b/test/unit/machine/machine_with_dynamic_initial_state_test.rb
new file mode 100644
index 0000000..13f49cc
--- /dev/null
+++ b/test/unit/machine/machine_with_dynamic_initial_state_test.rb
@@ -0,0 +1,65 @@
+require_relative '../../test_helper'
+
+class MachineWithDynamicInitialStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :initial_state
+    end
+    @machine = StateMachines::Machine.new(@klass, initial: lambda { |object| object.initial_state || :default })
+    @machine.state :parked, :idling, :default
+    @object = @klass.new
+  end
+
+  def test_should_have_dynamic_initial_state
+    assert @machine.dynamic_initial_state?
+  end
+
+  def test_should_use_the_record_for_determining_the_initial_state
+    @object.initial_state = :parked
+    assert_equal :parked, @machine.initial_state(@object).name
+
+    @object.initial_state = :idling
+    assert_equal :idling, @machine.initial_state(@object).name
+  end
+
+  def test_should_write_to_attribute_when_initializing_state
+    object = @klass.allocate
+    object.initial_state = :parked
+    @machine.initialize_state(object)
+    assert_equal 'parked', object.state
+  end
+
+  def test_should_set_initial_state_on_created_object
+    assert_equal 'default', @object.state
+  end
+
+  def test_should_not_set_initial_state_even_if_not_empty
+    @klass.class_eval do
+      def initialize(_attributes = {})
+        self.state = 'parked'
+        super()
+      end
+    end
+    object = @klass.new
+    assert_equal 'parked', object.state
+  end
+
+  def test_should_set_initial_state_after_initialization
+    base = Class.new do
+      attr_accessor :state_on_init
+
+      def initialize
+        self.state_on_init = state
+      end
+    end
+    klass = Class.new(base)
+    machine = StateMachines::Machine.new(klass, initial: lambda { |_object| :parked })
+    machine.state :parked
+
+    assert_nil klass.new.state_on_init
+  end
+
+  def test_should_not_be_included_in_known_states
+    assert_equal [:parked, :idling, :default], @machine.states.map { |state| state.name }
+  end
+end
diff --git a/test/unit/machine/machine_with_event_matchers_test.rb b/test/unit/machine/machine_with_event_matchers_test.rb
new file mode 100644
index 0000000..58350ba
--- /dev/null
+++ b/test/unit/machine/machine_with_event_matchers_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class MachineWithEventMatchersTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+  end
+
+  def test_should_empty_array_for_all_matcher
+    assert_equal [], @machine.event(StateMachines::AllMatcher.instance)
+  end
+
+  def test_should_return_referenced_events_for_blacklist_matcher
+    assert_instance_of StateMachines::Event, @machine.event(StateMachines::BlacklistMatcher.new([:park]))
+  end
+
+  def test_should_not_allow_configurations
+    exception = assert_raises(ArgumentError) { @machine.event(StateMachines::BlacklistMatcher.new([:park]), human_name: 'Park') }
+    assert_equal 'Cannot configure events when using matchers (using {:human_name=>"Park"})', exception.message
+  end
+
+  def test_should_track_referenced_events
+    @machine.event(StateMachines::BlacklistMatcher.new([:park]))
+    assert_equal [:park], @machine.events.map { |event| event.name }
+  end
+
+  def test_should_eval_context_for_matching_events
+    contexts_run = []
+    @machine.event(StateMachines::BlacklistMatcher.new([:park])) { contexts_run << name }
+
+    @machine.event :park
+    assert_equal [], contexts_run
+
+    @machine.event :ignite
+    assert_equal [:ignite], contexts_run
+
+    @machine.event :shift_up, :shift_down
+    assert_equal [:ignite, :shift_up, :shift_down], contexts_run
+  end
+end
+
diff --git a/test/unit/machine/machine_with_events_test.rb b/test/unit/machine/machine_with_events_test.rb
new file mode 100644
index 0000000..4cbec00
--- /dev/null
+++ b/test/unit/machine/machine_with_events_test.rb
@@ -0,0 +1,52 @@
+require_relative '../../test_helper'
+
+class MachineWithEventsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+  end
+
+  def test_should_return_the_created_event
+    assert_instance_of StateMachines::Event, @machine.event(:ignite)
+  end
+
+  def test_should_create_event_with_given_name
+    event = @machine.event(:ignite) {}
+    assert_equal :ignite, event.name
+  end
+
+  def test_should_evaluate_block_within_event_context
+    responded = false
+    @machine.event :ignite do
+      responded = respond_to?(:transition)
+    end
+
+    assert responded
+  end
+
+  def test_should_be_aliased_as_on
+    event = @machine.on(:ignite) {}
+    assert_equal :ignite, event.name
+  end
+
+  def test_should_have_events
+    event = @machine.event(:ignite)
+    assert_equal [event], @machine.events.to_a
+  end
+
+  def test_should_allow_human_state_name_lookup
+    @machine.event(:ignite)
+    assert_equal 'ignite', @klass.human_state_event_name(:ignite)
+  end
+
+  def test_should_raise_exception_on_invalid_human_state_event_name_lookup
+    exception = assert_raises(IndexError) { @klass.human_state_event_name(:invalid) }
+    assert_equal ':invalid is an invalid name', exception.message
+  end
+
+  def test_should_raise_exception_if_conflicting_type_used_for_name
+    @machine.event :park
+    exception = assert_raises(ArgumentError) {  @machine.event 'ignite' }
+    assert_equal '"ignite" event defined as String, :park defined as Symbol; all events must be consistent', exception.message
+  end
+end
diff --git a/test/unit/machine/machine_with_events_with_custom_human_names_test.rb b/test/unit/machine/machine_with_events_with_custom_human_names_test.rb
new file mode 100644
index 0000000..1aec611
--- /dev/null
+++ b/test/unit/machine/machine_with_events_with_custom_human_names_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class MachineWithEventsWithCustomHumanNamesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @event = @machine.event(:ignite, human_name: 'start')
+  end
+
+  def test_should_use_custom_human_name
+    assert_equal 'start', @event.human_name
+  end
+
+  def test_should_allow_human_state_name_lookup
+    assert_equal 'start', @klass.human_state_event_name(:ignite)
+  end
+end
+
diff --git a/test/unit/machine/machine_with_events_with_transitions_test.rb b/test/unit/machine/machine_with_events_with_transitions_test.rb
new file mode 100644
index 0000000..b30a744
--- /dev/null
+++ b/test/unit/machine/machine_with_events_with_transitions_test.rb
@@ -0,0 +1,37 @@
+require_relative '../../test_helper'
+
+class MachineWithEventsWithTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @event = @machine.event(:ignite) do
+      transition parked: :idling
+      transition stalled: :idling
+    end
+  end
+
+  def test_should_have_events
+    assert_equal [@event], @machine.events.to_a
+  end
+
+  def test_should_track_states_defined_in_event_transitions
+    assert_equal [:parked, :idling, :stalled], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_not_duplicate_states_defined_in_multiple_event_transitions
+    @machine.event :park do
+      transition idling: :parked
+    end
+
+    assert_equal [:parked, :idling, :stalled], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_track_state_from_new_events
+    @machine.event :shift_up do
+      transition idling: :first_gear
+    end
+
+    assert_equal [:parked, :idling, :stalled, :first_gear], @machine.states.map { |state| state.name }
+  end
+end
+
diff --git a/test/unit/machine/machine_with_existing_event_test.rb b/test/unit/machine/machine_with_existing_event_test.rb
new file mode 100644
index 0000000..40ab000
--- /dev/null
+++ b/test/unit/machine/machine_with_existing_event_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class MachineWithExistingEventTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @event = @machine.event(:ignite)
+    @same_event = @machine.event(:ignite)
+  end
+
+  def test_should_not_create_new_event
+    assert_same @event, @same_event
+  end
+
+  def test_should_allow_accessing_event_without_block
+    assert_equal @event, @machine.event(:ignite)
+  end
+end
diff --git a/test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb b/test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb
new file mode 100644
index 0000000..8c6129a
--- /dev/null
+++ b/test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class MachineWithExistingMachinesOnOwnerClassTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @second_machine = StateMachines::Machine.new(@klass, :status, initial: :idling)
+    @object = @klass.new
+  end
+
+  def test_should_track_each_state_machine
+    expected = { state: @machine, status: @second_machine }
+    assert_equal expected, @klass.state_machines
+  end
+
+  def test_should_initialize_state_for_both_machines
+    assert_equal 'parked', @object.state
+    assert_equal 'idling', @object.status
+  end
+end
diff --git a/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb b/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb
new file mode 100644
index 0000000..99d898e
--- /dev/null
+++ b/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb
@@ -0,0 +1,71 @@
+require_relative '../../test_helper'
+
+class MachineWithExistingMachinesWithSameAttributesOnOwnerClassTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @second_machine = StateMachines::Machine.new(@klass, :public_state, initial: :idling, attribute: :state)
+    @object = @klass.new
+  end
+
+  def test_should_track_each_state_machine
+    expected = { state: @machine, public_state: @second_machine }
+    assert_equal expected, @klass.state_machines
+  end
+
+  def test_should_write_to_state_only_once
+    @klass.class_eval do
+      attr_reader :write_count
+
+      def state=(_value)
+        @write_count ||= 0
+        @write_count += 1
+      end
+    end
+    object = @klass.new
+
+    assert_equal 1, object.write_count
+  end
+
+  def test_should_initialize_based_on_first_machine
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_allow_second_machine_to_initialize_state
+    @object.state = nil
+    @second_machine.initialize_state(@object)
+    assert_nil @object.state
+  end
+
+  def test_should_allow_transitions_on_both_machines
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @second_machine.event :park do
+      transition idling: :parked
+    end
+
+    @object.ignite
+    assert_equal 'idling', @object.state
+
+    @object.park
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_copy_new_states_to_sibling_machines
+    @first_gear = @machine.state :first_gear
+    assert_equal @first_gear, @second_machine.state(:first_gear)
+
+    @second_gear = @second_machine.state :second_gear
+    assert_equal @second_gear, @machine.state(:second_gear)
+  end
+
+  def test_should_copy_all_existing_states_to_new_machines
+    third_machine = StateMachines::Machine.new(@klass, :protected_state, attribute: :state)
+
+    assert_equal @machine.state(:parked), third_machine.state(:parked)
+    assert_equal @machine.state(:idling), third_machine.state(:idling)
+  end
+end
+
diff --git a/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb b/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb
new file mode 100644
index 0000000..f00d369
--- /dev/null
+++ b/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class MachineWithExistingMachinesWithSameAttributesOnOwnerSubclassTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @second_machine = StateMachines::Machine.new(@klass, :public_state, initial: :idling, attribute: :state)
+
+    @subclass = Class.new(@klass)
+    @object = @subclass.new
+  end
+
+  def test_should_not_copy_sibling_machines_to_subclass_after_initialization
+    @subclass.state_machine(:state) {}
+    assert_equal @klass.state_machine(:public_state), @subclass.state_machine(:public_state)
+  end
+
+  def test_should_copy_sibling_machines_to_subclass_after_new_state
+    subclass_machine = @subclass.state_machine(:state) {}
+    subclass_machine.state :first_gear
+    refute_equal @klass.state_machine(:public_state), @subclass.state_machine(:public_state)
+  end
+
+  def test_should_copy_new_states_to_sibling_machines
+    subclass_machine = @subclass.state_machine(:state) {}
+    @first_gear = subclass_machine.state :first_gear
+
+    second_subclass_machine = @subclass.state_machine(:public_state)
+    assert_equal @first_gear, second_subclass_machine.state(:first_gear)
+  end
+end
diff --git a/test/unit/machine/machine_with_existing_state_test.rb b/test/unit/machine/machine_with_existing_state_test.rb
new file mode 100644
index 0000000..0950ec3
--- /dev/null
+++ b/test/unit/machine/machine_with_existing_state_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class MachineWithExistingStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @state = @machine.state :parked
+    @same_state = @machine.state :parked, value: 1
+  end
+
+  def test_should_not_create_a_new_state
+    assert_same @state, @same_state
+  end
+
+  def test_should_update_attributes
+    assert_equal 1, @state.value
+  end
+
+  def test_should_no_longer_be_able_to_look_up_state_by_original_value
+    assert_nil @machine.states['parked', :value]
+  end
+
+  def test_should_be_able_to_look_up_state_by_new_value
+    assert_equal @state, @machine.states[1, :value]
+  end
+end
+
diff --git a/test/unit/machine/machine_with_failure_callbacks_test.rb b/test/unit/machine/machine_with_failure_callbacks_test.rb
new file mode 100644
index 0000000..929aa37
--- /dev/null
+++ b/test/unit/machine/machine_with_failure_callbacks_test.rb
@@ -0,0 +1,48 @@
+require_relative '../../test_helper'
+
+class MachineWithFailureCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :callbacks
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @event = @machine.event :ignite
+
+    @object = @klass.new
+    @object.callbacks = []
+  end
+
+  def test_should_raise_exception_if_implicit_option_specified
+    exception = assert_raises(ArgumentError) { @machine.after_failure invalid: :valid, do: lambda {} }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :on, :do, :if, :unless', exception.message
+  end
+
+  def test_should_raise_exception_if_method_not_specified
+    exception = assert_raises(ArgumentError) { @machine.after_failure on: :ignite }
+    assert_equal 'Method(s) for callback must be specified', exception.message
+  end
+
+  def test_should_invoke_callbacks_during_failed_transition
+    @machine.after_failure lambda { |object| object.callbacks << 'failure' }
+
+    @event.fire(@object)
+    assert_equal %w(failure), @object.callbacks
+  end
+
+  def test_should_allow_multiple_callbacks
+    @machine.after_failure lambda { |object| object.callbacks << 'failure1' }, lambda { |object| object.callbacks << 'failure2' }
+
+    @event.fire(@object)
+    assert_equal %w(failure1 failure2), @object.callbacks
+  end
+
+  def test_should_allow_multiple_callbacks_with_requirements
+    @machine.after_failure lambda { |object| object.callbacks << 'failure_ignite1' }, lambda { |object| object.callbacks << 'failure_ignite2' }, on: :ignite
+    @machine.after_failure lambda { |object| object.callbacks << 'failure_park1' }, lambda { |object| object.callbacks << 'failure_park2' }, on: :park
+
+    @event.fire(@object)
+    assert_equal %w(failure_ignite1 failure_ignite2), @object.callbacks
+  end
+end
+
diff --git a/test/unit/machine/machine_with_helpers_test.rb b/test/unit/machine/machine_with_helpers_test.rb
new file mode 100644
index 0000000..252938c
--- /dev/null
+++ b/test/unit/machine/machine_with_helpers_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class MachineWithHelpersTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @object = @klass.new
+  end
+
+  def test_should_throw_exception_with_invalid_scope
+    assert_raises(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.define_helper(:invalid, :park) {} }
+  end
+end
+
diff --git a/test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb b/test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb
new file mode 100644
index 0000000..3e605d9
--- /dev/null
+++ b/test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class MachineWithInitialStateWithValueAndOwnerDefault < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    state_machine_with_defaults = Class.new(StateMachines::Machine) do
+      def owner_class_attribute_default
+        0
+      end
+    end
+    @klass = Class.new
+    @machine = state_machine_with_defaults.new(@klass, initial: :parked) do
+      state :parked, value: 0
+    end
+  end
+
+  def test_should_not_warn_about_wrong_default
+    assert_equal '', $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
diff --git a/test/unit/machine/machine_with_initialize_and_super_test.rb b/test/unit/machine/machine_with_initialize_and_super_test.rb
new file mode 100644
index 0000000..f4b01af
--- /dev/null
+++ b/test/unit/machine/machine_with_initialize_and_super_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class MachineWithInitializeAndSuperTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def initialize
+        super()
+      end
+    end
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @object = @klass.new
+  end
+
+  def test_should_initialize_state
+    assert_equal 'parked', @object.state
+  end
+end
diff --git a/test/unit/machine/machine_with_initialize_arguments_and_block_test.rb b/test/unit/machine/machine_with_initialize_arguments_and_block_test.rb
new file mode 100644
index 0000000..0a57079
--- /dev/null
+++ b/test/unit/machine/machine_with_initialize_arguments_and_block_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class MachineWithInitializeArgumentsAndBlockTest < StateMachinesTest
+  def setup
+    @superclass = Class.new do
+      attr_reader :args
+      attr_reader :block_given
+
+      def initialize(*args)
+        @args = args
+        @block_given = block_given?
+      end
+    end
+    @klass = Class.new(@superclass)
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @object = @klass.new(1, 2, 3) {}
+  end
+
+  def test_should_initialize_state
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_preserve_arguments
+    assert_equal [1, 2, 3], @object.args
+  end
+
+  def test_should_preserve_block
+    assert @object.block_given
+  end
+end
+
diff --git a/test/unit/machine/machine_with_initialize_without_super_test.rb b/test/unit/machine/machine_with_initialize_without_super_test.rb
new file mode 100644
index 0000000..49bffee
--- /dev/null
+++ b/test/unit/machine/machine_with_initialize_without_super_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+
+class MachineWithInitializeWithoutSuperTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def initialize
+      end
+    end
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @object = @klass.new
+  end
+
+  def test_should_not_initialize_state
+    assert_nil @object.state
+  end
+end
+
diff --git a/test/unit/machine/machine_with_instance_helpers_test.rb b/test/unit/machine/machine_with_instance_helpers_test.rb
new file mode 100644
index 0000000..485b573
--- /dev/null
+++ b/test/unit/machine/machine_with_instance_helpers_test.rb
@@ -0,0 +1,179 @@
+require_relative '../../test_helper'
+
+class MachineWithInstanceHelpersTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @object = @klass.new
+  end
+
+  def test_should_not_redefine_existing_public_methods
+    @klass.class_eval do
+      def park
+        true
+      end
+    end
+
+    @machine.define_helper(:instance, :park) {}
+    assert_equal true, @object.park
+  end
+
+  def test_should_not_redefine_existing_protected_methods
+    @klass.class_eval do
+      protected
+      def park
+        true
+      end
+    end
+
+    @machine.define_helper(:instance, :park) {}
+    assert_equal true, @object.send(:park)
+  end
+
+  def test_should_not_redefine_existing_private_methods
+    @klass.class_eval do
+      private
+      def park
+        true
+      end
+    end
+
+    @machine.define_helper(:instance, :park) {}
+    assert_equal true, @object.send(:park)
+  end
+
+  def test_should_warn_if_defined_in_superclass
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    superclass = Class.new do
+      def park
+      end
+    end
+    klass = Class.new(superclass)
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:instance, :park) {}
+    assert_equal "Instance method \"park\" is already defined in #{superclass}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_warn_if_defined_in_multiple_superclasses
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    superclass1 = Class.new do
+      def park
+      end
+    end
+    superclass2 = Class.new(superclass1) do
+      def park
+      end
+    end
+    klass = Class.new(superclass2)
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:instance, :park) {}
+    assert_equal "Instance method \"park\" is already defined in #{superclass1}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_warn_if_defined_in_module_prior_to_helper_module
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    mod = Module.new do
+      def park
+      end
+    end
+    klass = Class.new do
+      include mod
+    end
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:instance, :park) {}
+    assert_equal "Instance method \"park\" is already defined in #{mod}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_not_warn_if_defined_in_module_after_helper_module
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    klass = Class.new
+    machine = StateMachines::Machine.new(klass)
+
+    mod = Module.new do
+      def park
+      end
+    end
+    klass.class_eval do
+      include mod
+    end
+
+    machine.define_helper(:instance, :park) {}
+    assert_equal '', $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
+    @original_stderr, $stderr = $stderr, StringIO.new
+    StateMachines::Machine.ignore_method_conflicts = true
+
+    superclass = Class.new do
+      def park
+      end
+    end
+    klass = Class.new(superclass)
+    machine = StateMachines::Machine.new(klass)
+
+    machine.define_helper(:instance, :park) { true }
+    assert_equal '', $stderr.string
+    assert_equal true, klass.new.park
+  ensure
+    StateMachines::Machine.ignore_method_conflicts = false
+    $stderr = @original_stderr
+  end
+
+  def test_should_define_nonexistent_methods
+    @machine.define_helper(:instance, :park) { false }
+    assert_equal false, @object.park
+  end
+
+  def test_should_warn_if_defined_multiple_times
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @machine.define_helper(:instance, :park) {}
+    @machine.define_helper(:instance, :park) {}
+
+    assert_equal "Instance method \"park\" is already defined in #{@klass} :state instance helpers, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  ensure
+    $stderr = @original_stderr
+  end
+
+  def test_should_pass_context_as_arguments
+    helper_args = nil
+    @machine.define_helper(:instance, :park) { |*args| helper_args = args }
+    @object.park
+    assert_equal 2, helper_args.length
+    assert_equal [@machine, @object], helper_args
+  end
+
+  def test_should_pass_method_arguments_through
+    helper_args = nil
+    @machine.define_helper(:instance, :park) { |*args| helper_args = args }
+    @object.park(1, 2, 3)
+    assert_equal 5, helper_args.length
+    assert_equal [@machine, @object, 1, 2, 3], helper_args
+  end
+
+  def test_should_allow_string_evaluation
+    @machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+      def park
+        false
+      end
+    end_eval
+    assert_equal false, @object.park
+  end
+end
+
diff --git a/test/unit/machine/machine_with_integration_test.rb b/test/unit/machine/machine_with_integration_test.rb
new file mode 100644
index 0000000..42a9be2
--- /dev/null
+++ b/test/unit/machine/machine_with_integration_test.rb
@@ -0,0 +1,72 @@
+require_relative '../../test_helper'
+
+class MachineWithIntegrationTest < StateMachinesTest
+
+  module Custom
+    include StateMachines::Integrations::Base
+
+    @defaults = {action: :save, use_transactions: false}
+
+    attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
+
+    def after_initialize
+      @initialized = true
+    end
+
+    def create_with_scope(name)
+      (@with_scopes ||= []) << name
+      lambda {}
+    end
+
+    def create_without_scope(name)
+      (@without_scopes ||= []) << name
+      lambda {}
+    end
+
+    def transaction(_)
+      @ran_transaction = true
+      yield
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(MachineWithIntegrationTest::Custom)
+
+
+    @machine = StateMachines::Machine.new(Class.new, integration: :custom)
+  end
+
+  def test_should_call_after_initialize_hook
+    assert @machine.initialized
+  end
+
+  def test_should_use_the_default_action
+    assert_equal :save, @machine.action
+  end
+
+  def test_should_use_the_custom_action_if_specified
+    machine = StateMachines::Machine.new(Class.new, integration: :custom, action: :save!)
+    assert_equal :save!, machine.action
+  end
+
+  def test_should_use_the_default_use_transactions
+    assert_equal false, @machine.use_transactions
+  end
+
+  def test_should_use_the_custom_use_transactions_if_specified
+    machine = StateMachines::Machine.new(Class.new, integration: :custom, use_transactions: true)
+    assert_equal true, machine.use_transactions
+  end
+
+  def test_should_define_a_singular_and_plural_with_scope
+    assert_equal %w(with_state with_states), @machine.with_scopes
+  end
+
+  def test_should_define_a_singular_and_plural_without_scope
+    assert_equal %w(without_state without_states), @machine.without_scopes
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/machine/machine_with_multiple_events_test.rb b/test/unit/machine/machine_with_multiple_events_test.rb
new file mode 100644
index 0000000..1235d2b
--- /dev/null
+++ b/test/unit/machine/machine_with_multiple_events_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class MachineWithMultipleEventsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @park, @shift_down = @machine.event(:park, :shift_down) do
+      transition first_gear: :parked
+    end
+  end
+
+  def test_should_have_events
+    assert_equal [@park, @shift_down], @machine.events.to_a
+  end
+
+  def test_should_define_transitions_for_each_event
+    [@park, @shift_down].each { |event| assert_equal 1, event.branches.size }
+  end
+
+  def test_should_transition_the_same_for_each_event
+    object = @klass.new
+    object.state = 'first_gear'
+    object.park
+    assert_equal 'parked', object.state
+
+    object = @klass.new
+    object.state = 'first_gear'
+    object.shift_down
+    assert_equal 'parked', object.state
+  end
+end
+
diff --git a/test/unit/machine/machine_with_namespace_test.rb b/test/unit/machine/machine_with_namespace_test.rb
new file mode 100644
index 0000000..552c5a7
--- /dev/null
+++ b/test/unit/machine/machine_with_namespace_test.rb
@@ -0,0 +1,48 @@
+require_relative '../../test_helper'
+
+class MachineWithNamespaceTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, namespace: 'alarm', initial: :active) do
+      event :enable do
+        transition off: :active
+      end
+
+      event :disable do
+        transition active: :off
+      end
+    end
+    @object = @klass.new
+  end
+
+  def test_should_namespace_state_predicates
+    [:alarm_active?, :alarm_off?].each do |name|
+      assert @object.respond_to?(name)
+    end
+  end
+
+  def test_should_namespace_event_checks
+    [:can_enable_alarm?, :can_disable_alarm?].each do |name|
+      assert @object.respond_to?(name)
+    end
+  end
+
+  def test_should_namespace_event_transition_readers
+    [:enable_alarm_transition, :disable_alarm_transition].each do |name|
+      assert @object.respond_to?(name)
+    end
+  end
+
+  def test_should_namespace_events
+    [:enable_alarm, :disable_alarm].each do |name|
+      assert @object.respond_to?(name)
+    end
+  end
+
+  def test_should_namespace_bang_events
+    [:enable_alarm!, :disable_alarm!].each do |name|
+      assert @object.respond_to?(name)
+    end
+  end
+end
+
diff --git a/test/unit/machine/machine_with_nil_action_test.rb b/test/unit/machine/machine_with_nil_action_test.rb
new file mode 100644
index 0000000..18b58d9
--- /dev/null
+++ b/test/unit/machine/machine_with_nil_action_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class MachineWithNilActionTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    @defaults = {action: :save}
+  end
+
+  def setup
+    StateMachines::Integrations.register(MachineWithNilActionTest::Custom)
+  end
+
+  def test_should_have_a_nil_action
+    machine = StateMachines::Machine.new(Class.new, action: nil, integration: :custom)
+    assert_nil machine.action
+  end
+
+  def test_should_have_default_action
+    machine = StateMachines::Machine.new(Class.new, integration: :custom)
+    assert_equal :save,  machine.action
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/machine/machine_with_other_states.rb b/test/unit/machine/machine_with_other_states.rb
new file mode 100644
index 0000000..4a0f56c
--- /dev/null
+++ b/test/unit/machine/machine_with_other_states.rb
@@ -0,0 +1,22 @@
+require_relative '../../test_helper'
+
+class MachineWithOtherStates < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @parked, @idling = @machine.other_states(:parked, :idling)
+  end
+
+  def test_should_include_other_states_in_known_states
+    assert_equal [@parked, @idling], @machine.states.to_a
+  end
+
+  def test_should_use_default_value
+    assert_equal 'idling', @idling.value
+  end
+
+  def test_should_not_create_matcher
+    assert_nil @idling.matcher
+  end
+end
+
diff --git a/test/unit/machine/machine_with_owner_subclass_test.rb b/test/unit/machine/machine_with_owner_subclass_test.rb
new file mode 100644
index 0000000..785f5b5
--- /dev/null
+++ b/test/unit/machine/machine_with_owner_subclass_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class MachineWithOwnerSubclassTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @subclass = Class.new(@klass)
+  end
+
+  def test_should_have_a_different_collection_of_state_machines
+    refute_same @klass.state_machines, @subclass.state_machines
+  end
+
+  def test_should_have_the_same_attribute_associated_state_machines
+    assert_equal @klass.state_machines, @subclass.state_machines
+  end
+end
+
diff --git a/test/unit/machine/machine_with_paths_test.rb b/test/unit/machine/machine_with_paths_test.rb
new file mode 100644
index 0000000..4739131
--- /dev/null
+++ b/test/unit/machine/machine_with_paths_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class MachineWithPathsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :shift_up do
+      transition first_gear: :second_gear
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_have_paths
+    assert_equal [[StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)]], @machine.paths_for(@object)
+  end
+
+  def test_should_allow_requirement_configuration
+    assert_equal [[StateMachines::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]], @machine.paths_for(@object, from: :first_gear)
+  end
+end
diff --git a/test/unit/machine/machine_with_private_action_test.rb b/test/unit/machine/machine_with_private_action_test.rb
new file mode 100644
index 0000000..33750f3
--- /dev/null
+++ b/test/unit/machine/machine_with_private_action_test.rb
@@ -0,0 +1,43 @@
+require_relative '../../test_helper'
+
+class MachineWithPrivateActionTest < StateMachinesTest
+  def setup
+    @superclass = Class.new do
+      private
+      def save
+      end
+    end
+    @klass = Class.new(@superclass)
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @object = @klass.new
+  end
+
+  def test_should_define_an_event_attribute_reader
+    assert @object.respond_to?(:state_event)
+  end
+
+  def test_should_define_an_event_attribute_writer
+    assert @object.respond_to?(:state_event=)
+  end
+
+  def test_should_define_an_event_transition_attribute_reader
+    assert @object.respond_to?(:state_event_transition, true)
+  end
+
+  def test_should_define_an_event_transition_attribute_writer
+    assert @object.respond_to?(:state_event_transition=, true)
+  end
+
+  def test_should_define_action
+    assert @klass.ancestors.any? { |ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.private_method_defined?(:save) }
+  end
+
+  def test_should_keep_action_private
+    assert @klass.private_method_defined?(:save)
+  end
+
+  def test_should_mark_action_hook_as_defined
+    assert @machine.action_hook?
+  end
+end
diff --git a/test/unit/machine/machine_with_state_matchers_test.rb b/test/unit/machine/machine_with_state_matchers_test.rb
new file mode 100644
index 0000000..91b1d0e
--- /dev/null
+++ b/test/unit/machine/machine_with_state_matchers_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class MachineWithStateMatchersTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+  end
+
+  def test_should_empty_array_for_all_matcher
+    assert_equal [], @machine.state(StateMachines::AllMatcher.instance)
+  end
+
+  def test_should_return_referenced_states_for_blacklist_matcher
+    assert_instance_of StateMachines::State, @machine.state(StateMachines::BlacklistMatcher.new([:parked]))
+  end
+
+  def test_should_not_allow_configurations
+    exception = assert_raises(ArgumentError) { @machine.state(StateMachines::BlacklistMatcher.new([:parked]), human_name: 'Parked') }
+    assert_equal 'Cannot configure states when using matchers (using {:human_name=>"Parked"})', exception.message
+  end
+
+  def test_should_track_referenced_states
+    @machine.state(StateMachines::BlacklistMatcher.new([:parked]))
+    assert_equal [nil, :parked], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_eval_context_for_matching_states
+    contexts_run = []
+    @machine.event(StateMachines::BlacklistMatcher.new([:parked])) { contexts_run << name }
+
+    @machine.event :parked
+    assert_equal [], contexts_run
+
+    @machine.event :idling
+    assert_equal [:idling], contexts_run
+
+    @machine.event :first_gear, :second_gear
+    assert_equal [:idling, :first_gear, :second_gear], contexts_run
+  end
+end
+
diff --git a/test/unit/machine/machine_with_state_with_matchers_test.rb b/test/unit/machine/machine_with_state_with_matchers_test.rb
new file mode 100644
index 0000000..852a422
--- /dev/null
+++ b/test/unit/machine/machine_with_state_with_matchers_test.rb
@@ -0,0 +1,19 @@
+require_relative '../../test_helper'
+
+class MachineWithStateWithMatchersTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @state = @machine.state :parked, if: ->(value) {!value.nil? }
+
+    @object = @klass.new
+    @object.state = 1
+  end
+
+  def test_should_use_custom_matcher
+    refute_nil @state.matcher
+    assert @state.matches?(1)
+    refute @state.matches?(nil)
+  end
+end
+
diff --git a/test/unit/machine/machine_with_states_test.rb b/test/unit/machine/machine_with_states_test.rb
new file mode 100644
index 0000000..9ae18ca
--- /dev/null
+++ b/test/unit/machine/machine_with_states_test.rb
@@ -0,0 +1,55 @@
+require_relative '../../test_helper'
+
+class MachineWithStatesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @parked, @idling = @machine.state :parked, :idling
+
+    @object = @klass.new
+  end
+
+  def test_should_have_states
+    assert_equal [nil, :parked, :idling], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_allow_state_lookup_by_name
+    assert_equal @parked, @machine.states[:parked]
+  end
+
+  def test_should_allow_state_lookup_by_value
+    assert_equal @parked, @machine.states['parked', :value]
+  end
+
+  def test_should_allow_human_state_name_lookup
+    assert_equal 'parked', @klass.human_state_name(:parked)
+  end
+
+  def test_should_raise_exception_on_invalid_human_state_name_lookup
+    exception = assert_raises(IndexError) { @klass.human_state_name(:invalid) }
+    assert_equal ':invalid is an invalid name', exception.message
+  end
+
+  def test_should_use_stringified_name_for_value
+    assert_equal 'parked', @parked.value
+  end
+
+  def test_should_not_use_custom_matcher
+    assert_nil @parked.matcher
+  end
+
+  def test_should_raise_exception_if_invalid_option_specified
+    exception = assert_raises(ArgumentError) { @machine.state(:first_gear, invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :value, :cache, :if, :human_name', exception.message
+  end
+
+  def test_should_raise_exception_if_conflicting_type_used_for_name
+    exception = assert_raises(ArgumentError) { @machine.state 'first_gear' }
+    assert_equal '"first_gear" state defined as String, :parked defined as Symbol; all states must be consistent', exception.message
+  end
+
+  def test_should_not_raise_exception_if_conflicting_type_is_nil_for_name
+    @machine.state nil
+  end
+end
+
diff --git a/test/unit/machine/machine_with_states_with_behaviors_test.rb b/test/unit/machine/machine_with_states_with_behaviors_test.rb
new file mode 100644
index 0000000..87cf8ad
--- /dev/null
+++ b/test/unit/machine/machine_with_states_with_behaviors_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class MachineWithStatesWithBehaviorsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+
+    @parked, @idling = @machine.state :parked, :idling do
+      def speed
+        0
+      end
+    end
+  end
+
+  def test_should_define_behaviors_for_each_state
+    refute_nil @parked.context_methods[:speed]
+    refute_nil @idling.context_methods[:speed]
+  end
+
+  def test_should_define_different_behaviors_for_each_state
+    refute_equal @parked.context_methods[:speed], @idling.context_methods[:speed]
+  end
+end
diff --git a/test/unit/machine/machine_with_states_with_custom_human_names_test.rb b/test/unit/machine/machine_with_states_with_custom_human_names_test.rb
new file mode 100644
index 0000000..6df4a9e
--- /dev/null
+++ b/test/unit/machine/machine_with_states_with_custom_human_names_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class MachineWithStatesWithCustomHumanNamesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @state = @machine.state :parked, human_name: 'stopped'
+  end
+
+  def test_should_use_custom_human_name
+    assert_equal 'stopped', @state.human_name
+  end
+
+  def test_should_allow_human_state_name_lookup
+    assert_equal 'stopped', @klass.human_state_name(:parked)
+  end
+end
+
diff --git a/test/unit/machine/machine_with_states_with_custom_values_test.rb b/test/unit/machine/machine_with_states_with_custom_values_test.rb
new file mode 100644
index 0000000..df2e2a8
--- /dev/null
+++ b/test/unit/machine/machine_with_states_with_custom_values_test.rb
@@ -0,0 +1,21 @@
+require_relative '../../test_helper'
+
+class MachineWithStatesWithCustomValuesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @state = @machine.state :parked, value: 1
+
+    @object = @klass.new
+    @object.state = 1
+  end
+
+  def test_should_use_custom_value
+    assert_equal 1, @state.value
+  end
+
+  def test_should_allow_lookup_by_custom_value
+    assert_equal @state, @machine.states[1, :value]
+  end
+end
+
diff --git a/test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb b/test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb
new file mode 100644
index 0000000..2f9c3ac
--- /dev/null
+++ b/test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb
@@ -0,0 +1,19 @@
+require_relative '../../test_helper'
+
+class MachineWithStatesWithRuntimeDependenciesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+  end
+
+  def test_should_not_evaluate_value_during_definition
+    @machine.state :parked, value: ->  { fail ArgumentError }
+  end
+
+  def test_should_not_evaluate_if_not_initial_state
+    @machine.state :parked, value: ->  { fail ArgumentError }
+    @klass.new
+  end
+end
+
diff --git a/test/unit/machine/machine_with_static_initial_state_test.rb b/test/unit/machine/machine_with_static_initial_state_test.rb
new file mode 100644
index 0000000..15abccc
--- /dev/null
+++ b/test/unit/machine/machine_with_static_initial_state_test.rb
@@ -0,0 +1,49 @@
+require_relative '../../test_helper'
+
+class MachineWithStaticInitialStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+  end
+
+  def test_should_not_have_dynamic_initial_state
+    refute @machine.dynamic_initial_state?
+  end
+
+  def test_should_have_an_initial_state
+    object = @klass.new
+    assert_equal 'parked', @machine.initial_state(object).value
+  end
+
+  def test_should_write_to_attribute_when_initializing_state
+    object = @klass.allocate
+    @machine.initialize_state(object)
+    assert_equal 'parked', object.state
+  end
+
+  def test_should_set_initial_on_state_object
+    assert @machine.state(:parked).initial
+  end
+
+  def test_should_set_initial_state_on_created_object
+    assert_equal 'parked', @klass.new.state
+  end
+
+  def test_should_not_initial_state_prior_to_initialization
+    base = Class.new do
+      attr_accessor :state_on_init
+
+      def initialize
+        self.state_on_init = state
+      end
+    end
+    klass = Class.new(base)
+    StateMachines::Machine.new(klass, initial: :parked)
+
+    assert_nil klass.new.state_on_init
+  end
+
+  def test_should_be_included_in_known_states
+    assert_equal [:parked], @machine.states.keys
+  end
+end
diff --git a/test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb b/test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb
new file mode 100644
index 0000000..426d53f
--- /dev/null
+++ b/test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+require 'stringio'
+
+class MachineWithSuperclassConflictingHelpersAfterDefinitionTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @superclass = Class.new
+    @klass = Class.new(@superclass)
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @superclass.class_eval do
+      def state?
+        true
+      end
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_call_superclass_attribute_predicate_without_arguments
+    assert @object.state?
+  end
+
+  def test_should_define_attribute_predicate_with_arguments
+    refute @object.state?(:parked)
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
+
diff --git a/test/unit/machine/machine_with_transition_callbacks_test.rb b/test/unit/machine/machine_with_transition_callbacks_test.rb
new file mode 100644
index 0000000..60eea74
--- /dev/null
+++ b/test/unit/machine/machine_with_transition_callbacks_test.rb
@@ -0,0 +1,144 @@
+require_relative '../../test_helper'
+
+class MachineWithTransitionCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :callbacks
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @event = @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.callbacks = []
+  end
+
+  def test_should_not_raise_exception_if_implicit_option_specified
+    @machine.before_transition invalid: :valid, do: -> {}
+  end
+
+  def test_should_raise_exception_if_method_not_specified
+    exception = assert_raises(ArgumentError) { @machine.before_transition to: :idling }
+    assert_equal 'Method(s) for callback must be specified', exception.message
+  end
+
+  def test_should_invoke_callbacks_during_transition
+    @machine.before_transition lambda { |object| object.callbacks << 'before' }
+    @machine.after_transition lambda { |object| object.callbacks << 'after' }
+    @machine.around_transition lambda { |object, _transition, block| object.callbacks << 'before_around'; block.call; object.callbacks << 'after_around' }
+
+    @event.fire(@object)
+    assert_equal %w(before before_around after_around after), @object.callbacks
+  end
+
+  def test_should_allow_multiple_callbacks
+    @machine.before_transition lambda { |object| object.callbacks << 'before1' }, lambda { |object| object.callbacks << 'before2' }
+    @machine.after_transition lambda { |object| object.callbacks << 'after1' }, lambda { |object| object.callbacks << 'after2' }
+    @machine.around_transition(
+        lambda { |object, _transition, block| object.callbacks << 'before_around1'; block.call; object.callbacks << 'after_around1' },
+        lambda { |object, _transition, block| object.callbacks << 'before_around2'; block.call; object.callbacks << 'after_around2' }
+    )
+
+    @event.fire(@object)
+    assert_equal %w(before1 before2 before_around1 before_around2 after_around2 after_around1 after1 after2), @object.callbacks
+  end
+
+  def test_should_allow_multiple_callbacks_with_requirements
+    @machine.before_transition lambda { |object| object.callbacks << 'before_parked1' }, lambda { |object| object.callbacks << 'before_parked2' }, from: :parked
+    @machine.before_transition lambda { |object| object.callbacks << 'before_idling1' }, lambda { |object| object.callbacks << 'before_idling2' }, from: :idling
+    @machine.after_transition lambda { |object| object.callbacks << 'after_parked1' }, lambda { |object| object.callbacks << 'after_parked2' }, from: :parked
+    @machine.after_transition lambda { |object| object.callbacks << 'after_idling1' }, lambda { |object| object.callbacks << 'after_idling2' }, from: :idling
+    @machine.around_transition(
+        lambda { |object, _transition, block| object.callbacks << 'before_around_parked1'; block.call; object.callbacks << 'after_around_parked1' },
+        lambda { |object, _transition, block| object.callbacks << 'before_around_parked2'; block.call; object.callbacks << 'after_around_parked2' },
+        from: :parked
+    )
+    @machine.around_transition(
+        lambda { |object, _transition, block| object.callbacks << 'before_around_idling1'; block.call; object.callbacks << 'after_around_idling1' },
+        lambda { |object, _transition, block| object.callbacks << 'before_around_idling2'; block.call; object.callbacks << 'after_around_idling2' },
+        from: :idling
+    )
+
+    @event.fire(@object)
+    assert_equal %w(before_parked1 before_parked2 before_around_parked1 before_around_parked2 after_around_parked2 after_around_parked1 after_parked1 after_parked2), @object.callbacks
+  end
+
+  def test_should_support_from_requirement
+    @machine.before_transition from: :parked, do: lambda { |object| object.callbacks << :parked }
+    @machine.before_transition from: :idling, do: lambda { |object| object.callbacks << :idling }
+
+    @event.fire(@object)
+    assert_equal [:parked], @object.callbacks
+  end
+
+  def test_should_support_except_from_requirement
+    @machine.before_transition except_from: :parked, do: lambda { |object| object.callbacks << :parked }
+    @machine.before_transition except_from: :idling, do: lambda { |object| object.callbacks << :idling }
+
+    @event.fire(@object)
+    assert_equal [:idling], @object.callbacks
+  end
+
+  def test_should_support_to_requirement
+    @machine.before_transition to: :parked, do: lambda { |object| object.callbacks << :parked }
+    @machine.before_transition to: :idling, do: lambda { |object| object.callbacks << :idling }
+
+    @event.fire(@object)
+    assert_equal [:idling], @object.callbacks
+  end
+
+  def test_should_support_except_to_requirement
+    @machine.before_transition except_to: :parked, do: lambda { |object| object.callbacks << :parked }
+    @machine.before_transition except_to: :idling, do: lambda { |object| object.callbacks << :idling }
+
+    @event.fire(@object)
+    assert_equal [:parked], @object.callbacks
+  end
+
+  def test_should_support_on_requirement
+    @machine.before_transition on: :park, do: lambda { |object| object.callbacks << :park }
+    @machine.before_transition on: :ignite, do: lambda { |object| object.callbacks << :ignite }
+
+    @event.fire(@object)
+    assert_equal [:ignite], @object.callbacks
+  end
+
+  def test_should_support_except_on_requirement
+    @machine.before_transition except_on: :park, do: lambda { |object| object.callbacks << :park }
+    @machine.before_transition except_on: :ignite, do: lambda { |object| object.callbacks << :ignite }
+
+    @event.fire(@object)
+    assert_equal [:park], @object.callbacks
+  end
+
+  def test_should_support_implicit_requirement
+    @machine.before_transition parked: :idling, do: lambda { |object| object.callbacks << :parked }
+    @machine.before_transition idling: :parked, do: lambda { |object| object.callbacks << :idling }
+
+    @event.fire(@object)
+    assert_equal [:parked], @object.callbacks
+  end
+
+  def test_should_track_states_defined_in_transition_callbacks
+    @machine.before_transition parked: :idling, do: lambda {}
+    @machine.after_transition first_gear: :second_gear, do: lambda {}
+    @machine.around_transition third_gear: :fourth_gear, do: lambda {}
+
+    assert_equal [:parked, :idling, :first_gear, :second_gear, :third_gear, :fourth_gear], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_not_duplicate_states_defined_in_multiple_event_transitions
+    @machine.before_transition parked: :idling, do: lambda {}
+    @machine.after_transition first_gear: :second_gear, do: lambda {}
+    @machine.after_transition parked: :idling, do: lambda {}
+    @machine.around_transition parked: :idling, do: lambda {}
+
+    assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_define_predicates_for_each_state
+    [:parked?, :idling?].each { |predicate| assert @object.respond_to?(predicate) }
+  end
+end
diff --git a/test/unit/machine/machine_with_transitions_test.rb b/test/unit/machine/machine_with_transitions_test.rb
new file mode 100644
index 0000000..1d707ec
--- /dev/null
+++ b/test/unit/machine/machine_with_transitions_test.rb
@@ -0,0 +1,87 @@
+require_relative '../../test_helper'
+
+class MachineWithTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+  end
+
+  def test_should_require_on_event
+    exception = assert_raises(ArgumentError) { @machine.transition(parked: :idling) }
+    assert_equal 'Must specify :on event', exception.message
+  end
+
+  def test_should_not_allow_except_on_option
+    exception = assert_raises(ArgumentError) { @machine.transition(except_on: :ignite, on: :ignite) }
+    assert_equal 'Unknown key: :except_on. Valid keys are: :from, :to, :except_from, :except_to, :if, :unless', exception.message
+  end
+
+  def test_should_allow_transitioning_without_a_to_state
+    @machine.transition(from: :parked, on: :ignite)
+  end
+
+  def test_should_allow_transitioning_without_a_from_state
+    @machine.transition(to: :idling, on: :ignite)
+  end
+
+  def test_should_allow_except_from_option
+    @machine.transition(except_from: :idling, on: :ignite)
+  end
+
+  def test_should_allow_except_to_option
+    @machine.transition(except_to: :parked, on: :ignite)
+  end
+
+  def test_should_allow_implicit_options
+    branch = @machine.transition(first_gear: :second_gear, on: :shift_up)
+    assert_instance_of StateMachines::Branch, branch
+
+    state_requirements = branch.state_requirements
+    assert_equal 1, state_requirements.length
+
+    assert_instance_of StateMachines::WhitelistMatcher, state_requirements[0][:from]
+    assert_equal [:first_gear], state_requirements[0][:from].values
+    assert_instance_of StateMachines::WhitelistMatcher, state_requirements[0][:to]
+    assert_equal [:second_gear], state_requirements[0][:to].values
+    assert_instance_of StateMachines::WhitelistMatcher, branch.event_requirement
+    assert_equal [:shift_up], branch.event_requirement.values
+  end
+
+  def test_should_allow_multiple_implicit_options
+    branch = @machine.transition(first_gear: :second_gear, second_gear: :third_gear, on: :shift_up)
+
+    state_requirements = branch.state_requirements
+    assert_equal 2, state_requirements.length
+  end
+
+  def test_should_allow_verbose_options
+    branch = @machine.transition(from: :parked, to: :idling, on: :ignite)
+    assert_instance_of StateMachines::Branch, branch
+  end
+
+  def test_should_include_all_transition_states_in_machine_states
+    @machine.transition(parked: :idling, on: :ignite)
+
+    assert_equal [:parked, :idling], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_include_all_transition_events_in_machine_events
+    @machine.transition(parked: :idling, on: :ignite)
+
+    assert_equal [:ignite], @machine.events.map { |event| event.name }
+  end
+
+  def test_should_allow_multiple_events
+    branches = @machine.transition(parked: :ignite, on: [:ignite, :shift_up])
+
+    assert_equal 2, branches.length
+    assert_equal [:ignite, :shift_up], @machine.events.map { |event| event.name }
+  end
+
+  def test_should_not_modify_options
+    options = { parked: :idling, on: :ignite }
+    @machine.transition(options)
+
+    assert_equal options, parked: :idling, on: :ignite
+  end
+end
diff --git a/test/unit/machine/machine_without_initialization_test.rb b/test/unit/machine/machine_without_initialization_test.rb
new file mode 100644
index 0000000..6158d6e
--- /dev/null
+++ b/test/unit/machine/machine_without_initialization_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class MachineWithoutInitializationTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def initialize(attributes = {})
+        attributes.each { |attr, value| send("#{attr}=", value) }
+        super()
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, initialize: false)
+  end
+
+  def test_should_not_have_an_initial_state
+    object = @klass.new
+    assert_nil object.state
+  end
+
+  def test_should_still_allow_manual_initialization
+    @klass.send(:include, Module.new do
+                          def initialize(_attributes = {})
+                            super()
+                            initialize_state_machines
+                          end
+                        end)
+
+    object = @klass.new
+    assert_equal 'parked', object.state
+  end
+end
diff --git a/test/unit/machine/machine_without_initialize_test.rb b/test/unit/machine/machine_without_initialize_test.rb
new file mode 100644
index 0000000..1b7faa2
--- /dev/null
+++ b/test/unit/machine/machine_without_initialize_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class MachineWithoutInitializeTest < StateMachinesTest
+  def setup
+    klass = Class.new
+    StateMachines::Machine.new(klass, initial: :parked)
+    @object = klass.new
+  end
+
+  def test_should_initialize_state
+    assert_equal 'parked', @object.state
+  end
+end
+
diff --git a/test/unit/machine/machine_without_integration_test.rb b/test/unit/machine/machine_without_integration_test.rb
new file mode 100644
index 0000000..cbf3e93
--- /dev/null
+++ b/test/unit/machine/machine_without_integration_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class MachineWithoutIntegrationTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @object = @klass.new
+  end
+
+  def test_transaction_should_yield
+    @yielded = false
+    @machine.within_transaction(@object) do
+      @yielded = true
+    end
+
+    assert @yielded
+  end
+
+  def test_invalidation_should_do_nothing
+    assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
+  end
+
+  def test_reset_should_do_nothing
+    assert_nil @machine.reset(@object)
+  end
+
+  def test_errors_for_should_be_empty
+    assert_equal '', @machine.errors_for(@object)
+  end
+end
+
diff --git a/test/unit/machine_collection/machine_collection_by_default_test.rb b/test/unit/machine_collection/machine_collection_by_default_test.rb
new file mode 100644
index 0000000..b2ed7a0
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_by_default_test.rb
@@ -0,0 +1,11 @@
+require_relative '../../test_helper'
+
+class MachineCollectionByDefaultTest < StateMachinesTest
+  def setup
+    @machines = StateMachines::MachineCollection.new
+  end
+
+  def test_should_not_have_any_machines
+    assert @machines.empty?
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_fire_attributes_with_validations_test.rb b/test/unit/machine_collection/machine_collection_fire_attributes_with_validations_test.rb
new file mode 100644
index 0000000..4399e23
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_fire_attributes_with_validations_test.rb
@@ -0,0 +1,72 @@
+require_relative '../../test_helper'
+
+class MachineCollectionFireWithValidationsTest < StateMachinesTest
+  module Custom
+    include StateMachines::Integrations::Base
+
+    def invalidate(object, _attribute, message, values = [])
+      (object.errors ||= []) << generate_message(message, values)
+    end
+
+    def reset(object)
+      object.errors = []
+    end
+  end
+
+  def setup
+    StateMachines::Integrations.register(MachineCollectionFireWithValidationsTest::Custom)
+
+    @klass = Class.new do
+      attr_accessor :errors
+
+      def initialize
+        @errors = []
+        super
+      end
+    end
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, integration: :custom)
+    @state.event :ignite do
+      transition parked: :idling
+    end
+
+    @machines[:alarm_state] = @alarm_state = StateMachines::Machine.new(@klass, :alarm_state, initial: :active, namespace: 'alarm', integration: :custom)
+    @alarm_state.event :disable do
+      transition active: :off
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_not_invalidate_if_transitions_exist
+    assert @machines.fire_events(@object, :ignite, :disable_alarm)
+    assert_equal [], @object.errors
+  end
+
+  def test_should_invalidate_if_no_transitions_exist
+    @object.state = 'idling'
+    @object.alarm_state = 'off'
+
+    refute @machines.fire_events(@object, :ignite, :disable_alarm)
+    assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors
+  end
+
+  def test_should_run_failure_callbacks_if_no_transitions_exist
+    @object.state = 'idling'
+    @object.alarm_state = 'off'
+    @state_failure_run = @alarm_state_failure_run = false
+
+    @machines[:state].after_failure { @state_failure_run = true }
+    @machines[:alarm_state].after_failure { @alarm_state_failure_run = true }
+
+    refute @machines.fire_events(@object, :ignite, :disable_alarm)
+    assert @state_failure_run
+    assert @alarm_state_failure_run
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
+
diff --git a/test/unit/machine_collection/machine_collection_fire_test.rb b/test/unit/machine_collection/machine_collection_fire_test.rb
new file mode 100644
index 0000000..f3801ca
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_fire_test.rb
@@ -0,0 +1,80 @@
+require_relative '../../test_helper'
+
+class MachineCollectionFireTest < StateMachinesTest
+  def setup
+    @machines = StateMachines::MachineCollection.new
+
+    @klass = Class.new do
+      attr_reader :saved
+
+      def save
+        @saved = true
+      end
+    end
+
+    # First machine
+    @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @state.event :ignite do
+      transition parked: :idling
+    end
+    @state.event :park do
+      transition idling: :parked
+    end
+
+    # Second machine
+    @machines[:alarm_state] = @alarm_state = StateMachines::Machine.new(@klass, :alarm_state, initial: :active, action: :save, namespace: 'alarm')
+    @alarm_state.event :enable do
+      transition off: :active
+    end
+    @alarm_state.event :disable do
+      transition active: :off
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_raise_exception_if_invalid_event_specified
+    exception = assert_raises(StateMachines::InvalidEvent) { @machines.fire_events(@object, :invalid) }
+    assert_equal :invalid, exception.event
+
+    exception = assert_raises(StateMachines::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) }
+    assert_equal :invalid, exception.event
+  end
+
+  def test_should_fail_if_any_event_cannot_transition
+    refute @machines.fire_events(@object, :park, :disable_alarm)
+    assert_equal 'parked', @object.state
+    assert_equal 'active', @object.alarm_state
+    refute @object.saved
+
+    refute @machines.fire_events(@object, :ignite, :enable_alarm)
+    assert_equal 'parked', @object.state
+    assert_equal 'active', @object.alarm_state
+    refute @object.saved
+  end
+
+  def test_should_run_failure_callbacks_if_any_event_cannot_transition
+    @state_failure_run = @alarm_state_failure_run = false
+
+    @machines[:state].after_failure { @state_failure_run = true }
+    @machines[:alarm_state].after_failure { @alarm_state_failure_run = true }
+
+    refute @machines.fire_events(@object, :park, :disable_alarm)
+    assert @state_failure_run
+    refute @alarm_state_failure_run
+  end
+
+  def test_should_be_successful_if_all_events_transition
+    assert @machines.fire_events(@object, :ignite, :disable_alarm)
+    assert_equal 'idling', @object.state
+    assert_equal 'off', @object.alarm_state
+    assert @object.saved
+  end
+
+  def test_should_not_save_if_skipping_action
+    assert @machines.fire_events(@object, :ignite, :disable_alarm, false)
+    assert_equal 'idling', @object.state
+    assert_equal 'off', @object.alarm_state
+    refute @object.saved
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb b/test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb
new file mode 100644
index 0000000..1ef66b2
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb
@@ -0,0 +1,54 @@
+require_relative '../../test_helper'
+
+class MachineCollectionFireAttributesWithValidationsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :errors
+
+      def initialize
+        @errors = []
+        super
+      end
+    end
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    class << @machine
+      def invalidate(object, _attribute, message, values = [])
+        (object.errors ||= []) << generate_message(message, values)
+      end
+
+      def reset(object)
+        object.errors = []
+      end
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_invalidate_if_event_is_invalid
+    @object.state_event = 'invalid'
+    @machines.transitions(@object, :save)
+
+    refute @object.errors.empty?
+  end
+
+  def test_should_invalidate_if_no_transition_exists
+    @object.state = 'idling'
+    @object.state_event = 'ignite'
+    @machines.transitions(@object, :save)
+
+    refute @object.errors.empty?
+  end
+
+  def test_should_not_invalidate_if_transition_exists
+    @object.state_event = 'ignite'
+    @machines.transitions(@object, :save)
+
+    assert @object.errors.empty?
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_fire_with_validations_test.rb b/test/unit/machine_collection/machine_collection_fire_with_validations_test.rb
new file mode 100644
index 0000000..53f72cf
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_fire_with_validations_test.rb
@@ -0,0 +1,76 @@
+require_relative '../../test_helper'
+
+module MachineCollectionFireWithValidationsIntegration
+  include StateMachines::Integrations::Base
+
+  def self.integration_name
+    :custom_validation
+  end
+
+  def invalidate(object, _attribute, message, values = [])
+    (object.errors ||= []) << generate_message(message, values)
+  end
+
+  def reset(object)
+    object.errors = []
+  end
+end
+
+class MachineCollectionFireWithValidationsTest < StateMachinesTest
+  def setup
+    StateMachines::Integrations.reset
+    StateMachines::Integrations.register(MachineCollectionFireWithValidationsIntegration)
+
+    @klass = Class.new do
+      attr_accessor :errors
+
+      def initialize
+        @errors = []
+        super
+      end
+    end
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, integration: :custom_validation)
+    @state.event :ignite do
+      transition parked: :idling
+    end
+
+    @machines[:alarm_state] = @alarm_state = StateMachines::Machine.new(@klass, :alarm_state, initial: :active, namespace: 'alarm', integration: :custom_validation)
+    @alarm_state.event :disable do
+      transition active: :off
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_not_invalidate_if_transitions_exist
+    assert @machines.fire_events(@object, :ignite, :disable_alarm)
+    assert_equal [], @object.errors
+  end
+
+  def test_should_invalidate_if_no_transitions_exist
+    @object.state = 'idling'
+    @object.alarm_state = 'off'
+
+    refute @machines.fire_events(@object, :ignite, :disable_alarm)
+    assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors
+  end
+
+  def test_should_run_failure_callbacks_if_no_transitions_exist
+    @object.state = 'idling'
+    @object.alarm_state = 'off'
+    @state_failure_run = @alarm_state_failure_run = false
+
+    @machines[:state].after_failure { @state_failure_run = true }
+    @machines[:alarm_state].after_failure { @alarm_state_failure_run = true }
+
+    refute @machines.fire_events(@object, :ignite, :disable_alarm)
+    assert @state_failure_run
+    assert @alarm_state_failure_run
+  end
+
+  def teardown
+    StateMachines::Integrations.reset
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_state_initialization_test.rb b/test/unit/machine_collection/machine_collection_state_initialization_test.rb
new file mode 100644
index 0000000..f4ca2fc
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_state_initialization_test.rb
@@ -0,0 +1,111 @@
+require_relative '../../test_helper'
+
+class MachineCollectionStateInitializationTest < StateMachinesTest
+  def setup
+    @machines = StateMachines::MachineCollection.new
+
+    @klass = Class.new
+
+    @machines[:state] = StateMachines::Machine.new(@klass, :state, initial: :parked)
+    @machines[:alarm_state] = StateMachines::Machine.new(@klass, :alarm_state, initial: ->(_object) { :active })
+    @machines[:alarm_state].state :active, value: -> { 'active' }
+
+    # Prevent the auto-initialization hook from firing
+    @klass.class_eval do
+      def initialize
+      end
+    end
+
+    @object = @klass.new
+    @object.state = nil
+    @object.alarm_state = nil
+  end
+
+  def test_should_raise_exception_if_invalid_option_specified
+    assert_raises(ArgumentError) { @machines.initialize_states(@object, invalid: true) }
+  end
+
+  def test_should_initialize_static_states_after_block
+    @machines.initialize_states(@object) do
+      @state_in_block = @object.state
+      @alarm_state_in_block = @object.alarm_state
+    end
+
+    assert_nil @state_in_block
+    assert_nil @alarm_state_in_block
+  end
+
+  def test_should_initialize_dynamic_states_after_block
+    @machines.initialize_states(@object) do
+      @alarm_state_in_block = @object.alarm_state
+    end
+
+    assert_nil @alarm_state_in_block
+    assert_equal 'active', @object.alarm_state
+  end
+
+  def test_should_initialize_all_states_without_block
+    @machines.initialize_states(@object)
+
+    assert_equal 'parked', @object.state
+    assert_equal 'active', @object.alarm_state
+  end
+
+  def test_should_skip_static_states_if_disabled
+    @machines.initialize_states(@object, static: false)
+    assert_nil @object.state
+    assert_equal 'active', @object.alarm_state
+  end
+
+  def test_should_initialize_existing_static_states_by_default
+    @object.state = 'idling'
+    @machines.initialize_states(@object)
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_initialize_existing_static_states_if_forced
+    @object.state = 'idling'
+    @machines.initialize_states(@object, static: :force)
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_initialize_existing_static_states_if_not_forced
+    @object.state = 'idling'
+    @machines.initialize_states(@object, static: true)
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_skip_dynamic_states_if_disabled
+    @machines.initialize_states(@object, dynamic: false)
+    assert_equal 'parked', @object.state
+    assert_nil @object.alarm_state
+  end
+
+  def test_should_not_initialize_existing_dynamic_states_by_default
+    @object.alarm_state = 'inactive'
+    @machines.initialize_states(@object)
+    assert_equal 'inactive', @object.alarm_state
+  end
+
+  def test_should_initialize_existing_dynamic_states_if_forced
+    @object.alarm_state = 'inactive'
+    @machines.initialize_states(@object, dynamic: :force)
+    assert_equal 'active', @object.alarm_state
+  end
+
+  def test_should_not_initialize_existing_dynamic_states_if_not_forced
+    @object.alarm_state = 'inactive'
+    @machines.initialize_states(@object, dynamic: true)
+    assert_equal 'inactive', @object.alarm_state
+  end
+
+  def test_shouldnt_force_state_given_either_as_string_or_symbol
+    @object.state = 'notparked'
+
+    @machines.initialize_states(@object, {}, { state: "parked" })
+    assert_equal 'notparked', @object.state
+
+    @machines.initialize_states(@object, {}, { "state" => "parked" })
+    assert_equal 'notparked', @object.state
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb b/test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb
new file mode 100644
index 0000000..3e17c53
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithBlankEventsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.state_event = ''
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_be_empty
+    assert @transitions.empty?
+  end
+
+  def test_should_perform
+    assert_equal true, @transitions.perform
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb b/test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb
new file mode 100644
index 0000000..726f6a1
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithCustomOptionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @transitions = @machines.transitions(@object, :save, after: false)
+  end
+
+  def test_should_use_custom_options
+    assert @transitions.skip_after
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb b/test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb
new file mode 100644
index 0000000..8212996
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb
@@ -0,0 +1,26 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithDifferentActionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @state.event :ignite do
+      transition parked: :idling
+    end
+    @machines[:status] = @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :persist)
+    @status.event :shift_up do
+      transition first_gear: :second_gear
+    end
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+    @object.status_event = 'shift_up'
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_only_select_matching_actions
+    assert_equal 1, @transitions.length
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb b/test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb
new file mode 100644
index 0000000..49a799c
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithExisitingTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.send(:state_event_transition=, StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling))
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_not_be_empty
+    assert_equal 1, @transitions.length
+  end
+
+  def test_should_perform
+    assert_equal true, @transitions.perform
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb b/test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb
new file mode 100644
index 0000000..588fe8a
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithInvalidEventsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.state_event = 'invalid'
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_be_empty
+    assert @transitions.empty?
+  end
+
+  def test_should_not_perform
+    assert_equal false, @transitions.perform
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb b/test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb
new file mode 100644
index 0000000..e67ce4d
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithSameActionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machines[:status] = @machine = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @machine.event :shift_up do
+      transition first_gear: :second_gear
+    end
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+    @object.status_event = 'shift_up'
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_not_be_empty
+    assert_equal 2, @transitions.length
+  end
+
+  def test_should_perform
+    assert_equal true, @transitions.perform
+  end
+end
+
diff --git a/test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb b/test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb
new file mode 100644
index 0000000..441a692
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb
@@ -0,0 +1,26 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithTransitionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_not_be_empty
+    assert_equal 1, @transitions.length
+  end
+
+  def test_should_perform
+    assert_equal true, @transitions.perform
+  end
+end
+
diff --git a/test/unit/machine_collection/machine_collection_transitions_without_events_test.rb b/test/unit/machine_collection/machine_collection_transitions_without_events_test.rb
new file mode 100644
index 0000000..eb8d2ed
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_without_events_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithoutEventsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.state_event = nil
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_be_empty
+    assert @transitions.empty?
+  end
+
+  def test_should_perform
+    assert_equal true, @transitions.perform
+  end
+end
diff --git a/test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb b/test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb
new file mode 100644
index 0000000..68d7dd9
--- /dev/null
+++ b/test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class MachineCollectionTransitionsWithoutTransitionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machines = StateMachines::MachineCollection.new
+    @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.state = 'idling'
+    @object.state_event = 'ignite'
+    @transitions = @machines.transitions(@object, :save)
+  end
+
+  def test_should_be_empty
+    assert @transitions.empty?
+  end
+
+  def test_should_not_perform
+    assert_equal false, @transitions.perform
+  end
+end
+
diff --git a/test/unit/matcher/all_matcher_test.rb b/test/unit/matcher/all_matcher_test.rb
new file mode 100644
index 0000000..8dae644
--- /dev/null
+++ b/test/unit/matcher/all_matcher_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class AllMatcherTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::AllMatcher.instance
+  end
+
+  def test_should_have_no_values
+    assert_equal [], @matcher.values
+  end
+
+  def test_should_always_match
+    [nil, :parked, :idling].each { |value| assert @matcher.matches?(value) }
+  end
+
+  def test_should_not_filter_any_values
+    assert_equal [:parked, :idling], @matcher.filter([:parked, :idling])
+  end
+
+  def test_should_generate_blacklist_matcher_after_subtraction
+    matcher = @matcher - [:parked, :idling]
+    assert_instance_of StateMachines::BlacklistMatcher, matcher
+    assert_equal [:parked, :idling], matcher.values
+  end
+
+  def test_should_have_a_description
+    assert_equal 'all', @matcher.description
+  end
+end
diff --git a/test/unit/matcher/blacklist_matcher_test.rb b/test/unit/matcher/blacklist_matcher_test.rb
new file mode 100644
index 0000000..b4dd31e
--- /dev/null
+++ b/test/unit/matcher/blacklist_matcher_test.rb
@@ -0,0 +1,30 @@
+require_relative '../../test_helper'
+
+class BlacklistMatcherTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::BlacklistMatcher.new([:parked, :idling])
+  end
+
+  def test_should_have_values
+    assert_equal [:parked, :idling], @matcher.values
+  end
+
+  def test_should_filter_known_values
+    assert_equal [:first_gear], @matcher.filter([:parked, :idling, :first_gear])
+  end
+
+  def test_should_match_unknown_values
+    assert @matcher.matches?(:first_gear)
+  end
+
+  def test_should_not_match_known_values
+    refute @matcher.matches?(:parked)
+  end
+
+  def test_should_have_a_description
+    assert_equal 'all - [:parked, :idling]', @matcher.description
+
+    matcher = StateMachines::BlacklistMatcher.new([:parked])
+    assert_equal 'all - :parked', matcher.description
+  end
+end
diff --git a/test/unit/matcher/loopback_matcher_test.rb b/test/unit/matcher/loopback_matcher_test.rb
new file mode 100644
index 0000000..1c424a5
--- /dev/null
+++ b/test/unit/matcher/loopback_matcher_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class LoopbackMatcherTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::LoopbackMatcher.instance
+  end
+
+  def test_should_have_no_values
+    assert_equal [], @matcher.values
+  end
+
+  def test_should_filter_all_values
+    assert_equal [], @matcher.filter([:parked, :idling])
+  end
+
+  def test_should_match_if_from_context_is_same
+    assert @matcher.matches?(:parked, from: :parked)
+  end
+
+  def test_should_not_match_if_from_context_is_different
+    refute @matcher.matches?(:parked, from: :idling)
+  end
+
+  def test_should_have_a_description
+    assert_equal 'same', @matcher.description
+  end
+end
diff --git a/test/unit/matcher/matcher_by_default_test.rb b/test/unit/matcher/matcher_by_default_test.rb
new file mode 100644
index 0000000..9c7be05
--- /dev/null
+++ b/test/unit/matcher/matcher_by_default_test.rb
@@ -0,0 +1,15 @@
+require_relative '../../test_helper'
+
+class MatcherByDefaultTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::Matcher.new
+  end
+
+  def test_should_have_no_values
+    assert_equal [], @matcher.values
+  end
+
+  def test_should_filter_all_values
+    assert_equal [], @matcher.filter([:parked, :idling])
+  end
+end
diff --git a/test/unit/matcher/matcher_with_multiple_values_test.rb b/test/unit/matcher/matcher_with_multiple_values_test.rb
new file mode 100644
index 0000000..6069660
--- /dev/null
+++ b/test/unit/matcher/matcher_with_multiple_values_test.rb
@@ -0,0 +1,15 @@
+require_relative '../../test_helper'
+
+class MatcherWithMultipleValuesTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::Matcher.new([:parked, :idling])
+  end
+
+  def test_should_have_values
+    assert_equal [:parked, :idling], @matcher.values
+  end
+
+  def test_should_filter_unknown_values
+    assert_equal [:parked], @matcher.filter([:parked, :first_gear])
+  end
+end
diff --git a/test/unit/matcher/matcher_with_value_test.rb b/test/unit/matcher/matcher_with_value_test.rb
new file mode 100644
index 0000000..cfd1255
--- /dev/null
+++ b/test/unit/matcher/matcher_with_value_test.rb
@@ -0,0 +1,15 @@
+require_relative '../../test_helper'
+
+class MatcherWithValueTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::Matcher.new(nil)
+  end
+
+  def test_should_have_values
+    assert_equal [nil], @matcher.values
+  end
+
+  def test_should_filter_unknown_values
+    assert_equal [nil], @matcher.filter([nil, :parked])
+  end
+end
diff --git a/test/unit/matcher/whitelist_matcher_test.rb b/test/unit/matcher/whitelist_matcher_test.rb
new file mode 100644
index 0000000..11e537d
--- /dev/null
+++ b/test/unit/matcher/whitelist_matcher_test.rb
@@ -0,0 +1,30 @@
+require_relative '../../test_helper'
+
+class WhitelistMatcherTest < StateMachinesTest
+  def setup
+    @matcher = StateMachines::WhitelistMatcher.new([:parked, :idling])
+  end
+
+  def test_should_have_values
+    assert_equal [:parked, :idling], @matcher.values
+  end
+
+  def test_should_filter_unknown_values
+    assert_equal [:parked, :idling], @matcher.filter([:parked, :idling, :first_gear])
+  end
+
+  def test_should_match_known_values
+    assert @matcher.matches?(:parked)
+  end
+
+  def test_should_not_match_unknown_values
+    refute @matcher.matches?(:first_gear)
+  end
+
+  def test_should_have_a_description
+    assert_equal '[:parked, :idling]', @matcher.description
+
+    matcher = StateMachines::WhitelistMatcher.new([:parked])
+    assert_equal ':parked', matcher.description
+  end
+end
diff --git a/test/unit/matcher_helpers/matcher_helpers_all_test.rb b/test/unit/matcher_helpers/matcher_helpers_all_test.rb
new file mode 100644
index 0000000..b85dcfb
--- /dev/null
+++ b/test/unit/matcher_helpers/matcher_helpers_all_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class MatcherHelpersAllTest < StateMachinesTest
+  include StateMachines::MatcherHelpers
+
+  def setup
+    @matcher = all
+  end
+
+  def test_should_build_an_all_matcher
+    assert_equal StateMachines::AllMatcher.instance, @matcher
+  end
+end
+
diff --git a/test/unit/matcher_helpers/matcher_helpers_any_test.rb b/test/unit/matcher_helpers/matcher_helpers_any_test.rb
new file mode 100644
index 0000000..7946e9f
--- /dev/null
+++ b/test/unit/matcher_helpers/matcher_helpers_any_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class MatcherHelpersAnyTest < StateMachinesTest
+  include StateMachines::MatcherHelpers
+
+  def setup
+    @matcher = any
+  end
+
+  def test_should_build_an_all_matcher
+    assert_equal StateMachines::AllMatcher.instance, @matcher
+  end
+end
+
diff --git a/test/unit/matcher_helpers/matcher_helpers_same_test.rb b/test/unit/matcher_helpers/matcher_helpers_same_test.rb
new file mode 100644
index 0000000..7a50202
--- /dev/null
+++ b/test/unit/matcher_helpers/matcher_helpers_same_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+
+class MatcherHelpersSameTest < StateMachinesTest
+  include StateMachines::MatcherHelpers
+
+  def setup
+    @matcher = same
+  end
+
+  def test_should_build_a_loopback_matcher
+    assert_equal StateMachines::LoopbackMatcher.instance, @matcher
+  end
+end
diff --git a/test/unit/node_collection/node_collection_after_being_copied_test.rb b/test/unit/node_collection/node_collection_after_being_copied_test.rb
new file mode 100644
index 0000000..aaa8c3d
--- /dev/null
+++ b/test/unit/node_collection/node_collection_after_being_copied_test.rb
@@ -0,0 +1,46 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionAfterBeingCopiedTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine)
+    @collection << @parked = Node.new(:parked)
+
+    @contexts_run = contexts_run = []
+    @collection.context([:parked]) { contexts_run << :parked }
+    @contexts_run.clear
+
+    @copied_collection = @collection.dup
+    @copied_collection << @idling = Node.new(:idling)
+    @copied_collection.context([:first_gear]) { contexts_run << :first_gear }
+  end
+
+  def test_should_not_modify_the_original_list
+    assert_equal 1, @collection.length
+    assert_equal 2, @copied_collection.length
+  end
+
+  def test_should_not_modify_the_indices
+    assert_nil @collection[:idling]
+    assert_equal @idling, @copied_collection[:idling]
+  end
+
+  def test_should_copy_each_node
+    refute_same @parked, @copied_collection[:parked]
+  end
+
+  def test_should_not_run_contexts
+    assert_equal [], @contexts_run
+  end
+
+  def test_should_not_modify_contexts
+    @collection << Node.new(:first_gear)
+    assert_equal [], @contexts_run
+  end
+
+  def test_should_copy_contexts
+    @copied_collection << Node.new(:parked)
+    refute @contexts_run.empty?
+  end
+end
diff --git a/test/unit/node_collection/node_collection_after_update_test.rb b/test/unit/node_collection/node_collection_after_update_test.rb
new file mode 100644
index 0000000..088d75d
--- /dev/null
+++ b/test/unit/node_collection/node_collection_after_update_test.rb
@@ -0,0 +1,36 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionAfterUpdateTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine, index: [:name, :value])
+
+    @parked = Node.new(:parked, 1)
+    @idling = Node.new(:idling, 2)
+
+    @collection << @parked << @idling
+
+    @parked.name = :parking
+    @parked.value = 0
+    @collection.update(@parked)
+  end
+
+  def test_should_not_change_the_index
+    assert_equal @parked, @collection.at(0)
+  end
+
+  def test_should_not_duplicate_in_the_collection
+    assert_equal 2, @collection.length
+  end
+
+  def test_should_add_each_indexed_key
+    assert_equal @parked, @collection[:parking]
+    assert_equal @parked, @collection[0, :value]
+  end
+
+  def test_should_remove_each_old_indexed_key
+    assert_nil @collection[:parked]
+    assert_nil @collection[1, :value]
+  end
+end
diff --git a/test/unit/node_collection/node_collection_by_default_test.rb b/test/unit/node_collection/node_collection_by_default_test.rb
new file mode 100644
index 0000000..795aadd
--- /dev/null
+++ b/test/unit/node_collection/node_collection_by_default_test.rb
@@ -0,0 +1,22 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionByDefaultTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(@machine)
+  end
+
+  def test_should_not_have_any_nodes
+    assert_equal 0, @collection.length
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @collection.machine
+  end
+
+  def test_should_index_by_name
+    @collection << object = Node.new(:parked)
+    assert_equal object, @collection[:parked]
+  end
+end
diff --git a/test/unit/node_collection/node_collection_test.rb b/test/unit/node_collection/node_collection_test.rb
new file mode 100644
index 0000000..d1784d5
--- /dev/null
+++ b/test/unit/node_collection/node_collection_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class NodeCollectionTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(@machine)
+  end
+
+  def test_should_raise_exception_if_invalid_option_specified
+    exception = assert_raises(ArgumentError) { StateMachines::NodeCollection.new(@machine, invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :index', exception.message
+  end
+
+  def test_should_raise_exception_on_lookup_if_invalid_index_specified
+    exception = assert_raises(ArgumentError) { @collection[:something, :invalid] }
+    assert_equal 'Invalid index: :invalid', exception.message
+  end
+
+  def test_should_raise_exception_on_fetch_if_invalid_index_specified
+    exception = assert_raises(ArgumentError) { @collection.fetch(:something, :invalid) }
+    assert_equal 'Invalid index: :invalid', exception.message
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_indices_test.rb b/test/unit/node_collection/node_collection_with_indices_test.rb
new file mode 100644
index 0000000..b15fe99
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_indices_test.rb
@@ -0,0 +1,42 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithIndicesTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine, index: [:name, :value])
+
+    @object = Node.new(:parked, 1)
+    @collection << @object
+  end
+
+  def test_should_use_first_index_by_default_on_key_retrieval
+    assert_equal [:parked], @collection.keys
+  end
+
+  def test_should_allow_customizing_index_for_key_retrieval
+    assert_equal [1], @collection.keys(:value)
+  end
+
+  def test_should_use_first_index_by_default_on_lookup
+    assert_equal @object, @collection[:parked]
+    assert_nil @collection[1]
+  end
+
+  def test_should_allow_customizing_index_on_lookup
+    assert_equal @object, @collection[1, :value]
+    assert_nil @collection[:parked, :value]
+  end
+
+  def test_should_use_first_index_by_default_on_fetch
+    assert_equal @object, @collection.fetch(:parked)
+    exception = assert_raises(IndexError) { @collection.fetch(1) }
+    assert_equal '1 is an invalid name', exception.message
+  end
+
+  def test_should_allow_customizing_index_on_fetch
+    assert_equal @object, @collection.fetch(1, :value)
+    exception = assert_raises(IndexError) { @collection.fetch(:parked, :value) }
+    assert_equal ':parked is an invalid value', exception.message
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_matcher_contexts_test.rb b/test/unit/node_collection/node_collection_with_matcher_contexts_test.rb
new file mode 100644
index 0000000..220c67c
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_matcher_contexts_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithMatcherContextsTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine)
+    @collection << Node.new(:parked)
+  end
+
+  def test_should_always_run_all_matcher_context
+    contexts_run = []
+    @collection.context([StateMachines::AllMatcher.instance]) { contexts_run << :all }
+    assert_equal [:all], contexts_run
+  end
+
+  def test_should_only_run_blacklist_matcher_if_not_matched
+    contexts_run = []
+    @collection.context([StateMachines::BlacklistMatcher.new([:parked])]) { contexts_run << :blacklist }
+    assert_equal [], contexts_run
+
+    @collection << Node.new(:idling)
+    assert_equal [:blacklist], contexts_run
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_nodes_test.rb b/test/unit/node_collection/node_collection_with_nodes_test.rb
new file mode 100644
index 0000000..22d32eb
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_nodes_test.rb
@@ -0,0 +1,46 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithNodesTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(@machine)
+
+    @parked = Node.new(:parked, nil, @machine)
+    @idling = Node.new(:idling, nil, @machine)
+
+    @collection << @parked
+    @collection << @idling
+  end
+
+  def test_should_be_able_to_enumerate
+    order = []
+    @collection.each { |object| order << object }
+
+    assert_equal [@parked, @idling], order
+  end
+
+  def test_should_be_able_to_concatenate_multiple_nodes
+    @first_gear = Node.new(:first_gear, nil, @machine)
+    @second_gear = Node.new(:second_gear, nil, @machine)
+    @collection.concat([@first_gear, @second_gear])
+
+    order = []
+    @collection.each { |object| order << object }
+    assert_equal [@parked, @idling, @first_gear, @second_gear], order
+  end
+
+  def test_should_be_able_to_access_by_index
+    assert_equal @parked, @collection.at(0)
+    assert_equal @idling, @collection.at(1)
+  end
+
+  def test_should_deep_copy_machine_changes
+    new_machine = StateMachines::Machine.new(Class.new)
+    @collection.machine = new_machine
+
+    assert_equal new_machine, @collection.machine
+    assert_equal new_machine, @parked.machine
+    assert_equal new_machine, @idling.machine
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_numeric_index_test.rb b/test/unit/node_collection/node_collection_with_numeric_index_test.rb
new file mode 100644
index 0000000..1544e4c
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_numeric_index_test.rb
@@ -0,0 +1,24 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithNumericIndexTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine, index: [:name, :value])
+
+    @parked = Node.new(10, 1)
+    @collection << @parked
+  end
+
+  def test_should_index_by_name
+    assert_equal @parked, @collection[10]
+  end
+
+  def test_should_index_by_string_name
+    assert_equal @parked, @collection['10']
+  end
+
+  def test_should_index_by_symbol_name
+    assert_equal @parked, @collection[:'10']
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb b/test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb
new file mode 100644
index 0000000..55e6a4e
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb
@@ -0,0 +1,22 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithPostdefinedContextsTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine)
+    @collection << Node.new(:parked)
+  end
+
+  def test_should_run_context_if_matched
+    contexts_run = []
+    @collection.context([:parked]) { contexts_run << :parked }
+    assert_equal [:parked], contexts_run
+  end
+
+  def test_should_not_run_contexts_if_not_matched
+    contexts_run = []
+    @collection.context([:idling]) { contexts_run << :idling }
+    assert_equal [], contexts_run
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_predefined_contexts_test.rb b/test/unit/node_collection/node_collection_with_predefined_contexts_test.rb
new file mode 100644
index 0000000..7cc9089
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_predefined_contexts_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithPredefinedContextsTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine)
+
+    @contexts_run = contexts_run = []
+    @collection.context([:parked]) { contexts_run << :parked }
+    @collection.context([:parked]) { contexts_run << :second_parked }
+  end
+
+  def test_should_run_contexts_in_the_order_defined
+    @collection << Node.new(:parked)
+    assert_equal [:parked, :second_parked], @contexts_run
+  end
+
+  def test_should_not_run_contexts_if_not_matched
+    @collection << Node.new(:idling)
+    assert_equal [], @contexts_run
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_string_index_test.rb b/test/unit/node_collection/node_collection_with_string_index_test.rb
new file mode 100644
index 0000000..77f7a38
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_string_index_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithStringIndexTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine, index: [:name, :value])
+
+    @parked = Node.new(:parked, 1)
+    @collection << @parked
+  end
+
+  def test_should_index_by_name
+    assert_equal @parked, @collection[:parked]
+  end
+
+  def test_should_index_by_string_name
+    assert_equal @parked, @collection['parked']
+  end
+end
diff --git a/test/unit/node_collection/node_collection_with_symbol_index_test.rb b/test/unit/node_collection/node_collection_with_symbol_index_test.rb
new file mode 100644
index 0000000..1d488f5
--- /dev/null
+++ b/test/unit/node_collection/node_collection_with_symbol_index_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+require_relative '../../files/node'
+
+class NodeCollectionWithSymbolIndexTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine, index: [:name, :value])
+
+    @parked = Node.new('parked', 1)
+    @collection << @parked
+  end
+
+  def test_should_index_by_name
+    assert_equal @parked, @collection['parked']
+  end
+
+  def test_should_index_by_symbol_name
+    assert_equal @parked, @collection[:parked]
+  end
+end
diff --git a/test/unit/node_collection/node_collection_without_indices_test.rb b/test/unit/node_collection/node_collection_without_indices_test.rb
new file mode 100644
index 0000000..671b126
--- /dev/null
+++ b/test/unit/node_collection/node_collection_without_indices_test.rb
@@ -0,0 +1,30 @@
+require_relative '../../test_helper'
+
+class NodeCollectionWithoutIndicesTest < StateMachinesTest
+  def setup
+    machine = StateMachines::Machine.new(Class.new)
+    @collection = StateMachines::NodeCollection.new(machine, index: {})
+  end
+
+  def test_should_allow_adding_node
+    @collection << Object.new
+    assert_equal 1, @collection.length
+  end
+
+  def test_should_not_allow_keys_retrieval
+    exception = assert_raises(ArgumentError) { @collection.keys }
+    assert_equal 'No indices configured', exception.message
+  end
+
+  def test_should_not_allow_lookup
+    @collection << Object.new
+    exception = assert_raises(ArgumentError) { @collection[0] }
+    assert_equal 'No indices configured', exception.message
+  end
+
+  def test_should_not_allow_fetching
+    @collection << Object.new
+    exception = assert_raises(ArgumentError) { @collection.fetch(0) }
+    assert_equal 'No indices configured', exception.message
+  end
+end
diff --git a/test/unit/path/path_by_default_test.rb b/test/unit/path/path_by_default_test.rb
new file mode 100644
index 0000000..43f7a1e
--- /dev/null
+++ b/test/unit/path/path_by_default_test.rb
@@ -0,0 +1,54 @@
+require_relative '../../test_helper'
+
+class PathByDefaultTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @object = @klass.new
+
+    @path = StateMachines::Path.new(@object, @machine)
+  end
+
+  def test_should_have_an_object
+    assert_equal @object, @path.object
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @path.machine
+  end
+
+  def test_should_not_have_walked_anywhere
+    assert_equal [], @path
+  end
+
+  def test_should_not_have_a_from_name
+    assert_nil @path.from_name
+  end
+
+  def test_should_have_no_from_states
+    assert_equal [], @path.from_states
+  end
+
+  def test_should_not_have_a_to_name
+    assert_nil @path.to_name
+  end
+
+  def test_should_have_no_to_states
+    assert_equal [], @path.to_states
+  end
+
+  def test_should_have_no_events
+    assert_equal [], @path.events
+  end
+
+  def test_should_not_be_able_to_walk_anywhere
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+
+  def test_should_not_be_complete
+    assert_equal false, @path.complete?
+  end
+end
+
diff --git a/test/unit/path/path_test.rb b/test/unit/path/path_test.rb
new file mode 100644
index 0000000..983066e
--- /dev/null
+++ b/test/unit/path/path_test.rb
@@ -0,0 +1,14 @@
+require_relative '../../test_helper'
+
+class PathTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @object = @klass.new
+  end
+
+  def test_should_raise_exception_if_invalid_option_specified
+    exception = assert_raises(ArgumentError) { StateMachines::Path.new(@object, @machine, invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :target, :guard', exception.message
+  end
+end
\ No newline at end of file
diff --git a/test/unit/path/path_with_available_transitions_after_reaching_target_test.rb b/test/unit/path/path_with_available_transitions_after_reaching_target_test.rb
new file mode 100644
index 0000000..589256d
--- /dev/null
+++ b/test/unit/path/path_with_available_transitions_after_reaching_target_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class PathWithAvailableTransitionsAfterReachingTargetTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :shift_up do
+      transition parked: :first_gear
+    end
+    @machine.event :park do
+      transition [:idling, :first_gear] => :parked
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine, target: :parked)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     @park_transition = StateMachines::Transition.new(@object, @machine, :park, :idling, :parked)
+                 ])
+  end
+
+  def test_should_be_complete
+    assert_equal true, @path.complete?
+  end
+
+  def test_should_be_able_to_walk
+    paths = []
+    @path.walk { |path| paths << path }
+    assert_equal [
+                     [@ignite_transition, @park_transition, StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)]
+                 ], paths
+  end
+end
+
diff --git a/test/unit/path/path_with_available_transitions_test.rb b/test/unit/path/path_with_available_transitions_test.rb
new file mode 100644
index 0000000..72a4dcb
--- /dev/null
+++ b/test/unit/path/path_with_available_transitions_test.rb
@@ -0,0 +1,54 @@
+require_relative '../../test_helper'
+
+class PathWithAvailableTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling, :first_gear
+    @machine.event :ignite
+    @machine.event :shift_up do
+      transition idling: :first_gear
+    end
+    @machine.event :park do
+      transition idling: :parked
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+                 ])
+  end
+
+  def test_should_not_be_complete
+    refute @path.complete?
+  end
+
+  def test_should_walk_each_available_transition
+    paths = []
+    @path.walk { |path| paths << path }
+
+    assert_equal [
+                     [@ignite_transition, StateMachines::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)],
+                     [@ignite_transition, StateMachines::Transition.new(@object, @machine, :park, :idling, :parked)]
+                 ], paths
+  end
+
+  def test_should_yield_path_instances_when_walking
+    @path.walk do |path|
+      assert_instance_of StateMachines::Path, path
+    end
+  end
+
+  def test_should_not_modify_current_path_after_walking
+    @path.walk {}
+    assert_equal [@ignite_transition], @path
+  end
+
+  def test_should_not_modify_object_after_walking
+    @path.walk {}
+    assert_equal 'parked', @object.state
+  end
+end
diff --git a/test/unit/path/path_with_deep_target_reached_test.rb b/test/unit/path/path_with_deep_target_reached_test.rb
new file mode 100644
index 0000000..7c1ea6a
--- /dev/null
+++ b/test/unit/path/path_with_deep_target_reached_test.rb
@@ -0,0 +1,50 @@
+require_relative '../../test_helper'
+
+class PathWithDeepTargetReachedTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :shift_up do
+      transition parked: :first_gear
+    end
+    @machine.event :park do
+      transition [:idling, :first_gear] => :parked
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine, target: :parked)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     @park_transition = StateMachines::Transition.new(@object, @machine, :park, :idling, :parked),
+                     @shift_up_transition = StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :first_gear),
+                     @park_transition_2 = StateMachines::Transition.new(@object, @machine, :park, :first_gear, :parked)
+                 ])
+  end
+
+  def test_should_be_complete
+    assert_equal true, @path.complete?
+  end
+
+  def test_should_not_be_able_to_walk
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+
+  def test_should_not_be_able_to_walk_with_available_transitions
+    @machine.event :park do
+      transition parked: same
+    end
+
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+end
+
diff --git a/test/unit/path/path_with_deep_target_test.rb b/test/unit/path/path_with_deep_target_test.rb
new file mode 100644
index 0000000..e0ae6b1
--- /dev/null
+++ b/test/unit/path/path_with_deep_target_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class PathWithDeepTargetTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :shift_up do
+      transition parked: :first_gear
+    end
+    @machine.event :park do
+      transition [:idling, :first_gear] => :parked
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine, target: :parked)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     @park_transition = StateMachines::Transition.new(@object, @machine, :park, :idling, :parked),
+                     @shift_up_transition = StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)
+                 ])
+  end
+
+  def test_should_not_be_complete
+    assert_equal false, @path.complete?
+  end
+
+  def test_should_be_able_to_walk
+    paths = []
+    @path.walk { |path| paths << path }
+    assert_equal [
+                     [@ignite_transition, @park_transition, @shift_up_transition, StateMachines::Transition.new(@object, @machine, :park, :first_gear, :parked)]
+                 ], paths
+  end
+end
diff --git a/test/unit/path/path_with_duplicates_test.rb b/test/unit/path/path_with_duplicates_test.rb
new file mode 100644
index 0000000..a651473
--- /dev/null
+++ b/test/unit/path/path_with_duplicates_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class PathWithDuplicatesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :park, :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     @park_transition = StateMachines::Transition.new(@object, @machine, :park, :idling, :parked),
+                     @ignite_again_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+                 ])
+  end
+
+  def test_should_not_include_duplicates_in_from_states
+    assert_equal [:parked, :idling], @path.from_states
+  end
+
+  def test_should_not_include_duplicates_in_to_states
+    assert_equal [:idling, :parked], @path.to_states
+  end
+
+  def test_should_not_include_duplicates_in_events
+    assert_equal [:ignite, :park], @path.events
+  end
+end
diff --git a/test/unit/path/path_with_encountered_transitions_test.rb b/test/unit/path/path_with_encountered_transitions_test.rb
new file mode 100644
index 0000000..88f6793
--- /dev/null
+++ b/test/unit/path/path_with_encountered_transitions_test.rb
@@ -0,0 +1,34 @@
+require_relative '../../test_helper'
+
+class PathWithEncounteredTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling, :first_gear
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :park do
+      transition idling: :parked
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     @park_transition = StateMachines::Transition.new(@object, @machine, :park, :idling, :parked)
+                 ])
+  end
+
+  def test_should_be_complete
+    assert_equal true, @path.complete?
+  end
+
+  def test_should_not_be_able_to_walk
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+end
diff --git a/test/unit/path/path_with_guarded_transitions_test.rb b/test/unit/path/path_with_guarded_transitions_test.rb
new file mode 100644
index 0000000..4514530
--- /dev/null
+++ b/test/unit/path/path_with_guarded_transitions_test.rb
@@ -0,0 +1,42 @@
+require_relative '../../test_helper'
+
+class PathWithGuardedTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+    @machine.event :shift_up do
+      transition idling: :first_gear, if: lambda { false }
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_not_walk_transitions_if_guard_enabled
+    path = StateMachines::Path.new(@object, @machine)
+    path.concat([
+                    StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+                ])
+
+    paths = []
+    path.walk { |next_path| paths << next_path }
+
+    assert_equal [], paths
+  end
+
+  def test_should_not_walk_transitions_if_guard_disabled
+    path = StateMachines::Path.new(@object, @machine, guard: false)
+    path.concat([
+                    ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+                ])
+
+    paths = []
+    path.walk { |next_path| paths << next_path }
+
+    assert_equal [
+                     [ignite_transition, StateMachines::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)]
+                 ], paths
+  end
+end
diff --git a/test/unit/path/path_with_reached_target_test.rb b/test/unit/path/path_with_reached_target_test.rb
new file mode 100644
index 0000000..bcd7334
--- /dev/null
+++ b/test/unit/path/path_with_reached_target_test.rb
@@ -0,0 +1,35 @@
+require_relative '../../test_helper'
+
+class PathWithReachedTargetTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :park do
+      transition idling: :parked
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine, target: :parked)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     @park_transition = StateMachines::Transition.new(@object, @machine, :park, :idling, :parked)
+                 ])
+  end
+
+  def test_should_be_complete
+    assert_equal true, @path.complete?
+  end
+
+  def test_should_not_be_able_to_walk
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+end
+
diff --git a/test/unit/path/path_with_transitions_test.rb b/test/unit/path/path_with_transitions_test.rb
new file mode 100644
index 0000000..e747563
--- /dev/null
+++ b/test/unit/path/path_with_transitions_test.rb
@@ -0,0 +1,54 @@
+require_relative '../../test_helper'
+
+class PathWithTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling, :first_gear
+    @machine.event :ignite, :shift_up
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+                     @shift_up_transition = StateMachines::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)
+                 ])
+  end
+
+  def test_should_enumerate_transitions
+    assert_equal [@ignite_transition, @shift_up_transition], @path
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :parked, @path.from_name
+  end
+
+  def test_should_have_from_states
+    assert_equal [:parked, :idling], @path.from_states
+  end
+
+  def test_should_have_a_to_name
+    assert_equal :first_gear, @path.to_name
+  end
+
+  def test_should_have_to_states
+    assert_equal [:idling, :first_gear], @path.to_states
+  end
+
+  def test_should_have_events
+    assert_equal [:ignite, :shift_up], @path.events
+  end
+
+  def test_should_not_be_able_to_walk_anywhere
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+
+  def test_should_be_complete
+    assert_equal true, @path.complete?
+  end
+end
+
diff --git a/test/unit/path/path_with_unreached_target_test.rb b/test/unit/path/path_with_unreached_target_test.rb
new file mode 100644
index 0000000..0c3b3db
--- /dev/null
+++ b/test/unit/path/path_with_unreached_target_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class PathWithUnreachedTargetTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @path = StateMachines::Path.new(@object, @machine, target: :parked)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+                 ])
+  end
+
+  def test_should_not_be_complete
+    assert_equal false, @path.complete?
+  end
+
+  def test_should_not_be_able_to_walk
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+end
+
diff --git a/test/unit/path/path_without_transitions_test.rb b/test/unit/path/path_without_transitions_test.rb
new file mode 100644
index 0000000..8be5cd5
--- /dev/null
+++ b/test/unit/path/path_without_transitions_test.rb
@@ -0,0 +1,24 @@
+require_relative '../../test_helper'
+
+class PathWithoutTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+
+    @path = StateMachines::Path.new(@object, @machine)
+    @path.concat([
+                     @ignite_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+                 ])
+  end
+
+  def test_should_not_be_able_to_walk_anywhere
+    walked = false
+    @path.walk { walked = true }
+    assert_equal false, walked
+  end
+end
+
diff --git a/test/unit/path_collection/path_collection_by_default_test.rb b/test/unit/path_collection/path_collection_by_default_test.rb
new file mode 100644
index 0000000..1720166
--- /dev/null
+++ b/test/unit/path_collection/path_collection_by_default_test.rb
@@ -0,0 +1,46 @@
+require_relative '../../test_helper'
+
+class PathCollectionByDefaultTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @paths = StateMachines::PathCollection.new(@object, @machine)
+  end
+
+  def test_should_have_an_object
+    assert_equal @object, @paths.object
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @paths.machine
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :parked, @paths.from_name
+  end
+
+  def test_should_not_have_a_to_name
+    assert_nil @paths.to_name
+  end
+
+  def test_should_have_no_from_states
+    assert_equal [], @paths.from_states
+  end
+
+  def test_should_have_no_to_states
+    assert_equal [], @paths.to_states
+  end
+
+  def test_should_have_no_events
+    assert_equal [], @paths.events
+  end
+
+  def test_should_have_no_paths
+    assert @paths.empty?
+  end
+end
diff --git a/test/unit/path_collection/path_collection_test.rb b/test/unit/path_collection/path_collection_test.rb
new file mode 100644
index 0000000..b875168
--- /dev/null
+++ b/test/unit/path_collection/path_collection_test.rb
@@ -0,0 +1,24 @@
+require_relative '../../test_helper'
+
+class PathCollectionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @object = @klass.new
+  end
+
+  def test_should_raise_exception_if_invalid_option_specified
+    exception = assert_raises(ArgumentError) { StateMachines::PathCollection.new(@object, @machine, invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :from, :to, :deep, :guard', exception.message
+  end
+
+  def test_should_raise_exception_if_invalid_from_state_specified
+    exception = assert_raises(IndexError) { StateMachines::PathCollection.new(@object, @machine, from: :invalid) }
+    assert_equal ':invalid is an invalid name', exception.message
+  end
+
+  def test_should_raise_exception_if_invalid_to_state_specified
+    exception = assert_raises(IndexError) { StateMachines::PathCollection.new(@object, @machine, to: :invalid) }
+    assert_equal ':invalid is an invalid name', exception.message
+  end
+end
diff --git a/test/unit/path_collection/path_collection_with_deep_paths_test.rb b/test/unit/path_collection/path_collection_with_deep_paths_test.rb
new file mode 100644
index 0000000..abbaf3c
--- /dev/null
+++ b/test/unit/path_collection/path_collection_with_deep_paths_test.rb
@@ -0,0 +1,43 @@
+require_relative '../../test_helper'
+
+class PathCollectionWithDeepPathsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :shift_up do
+      transition parked: :idling, idling: :first_gear
+    end
+    @machine.event :shift_down do
+      transition first_gear: :idling
+    end
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @paths = StateMachines::PathCollection.new(@object, @machine, to: :idling, deep: true)
+  end
+
+  def test_should_allow_target_to_be_reached_more_than_once_per_path
+    assert_equal [
+      [
+        StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+      ],
+      [
+        StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+        StateMachines::Transition.new(@object, @machine, :shift_up, :idling, :first_gear),
+        StateMachines::Transition.new(@object, @machine, :shift_down, :first_gear, :idling)
+      ],
+      [
+        StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :idling)
+      ],
+      [
+        StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :idling),
+        StateMachines::Transition.new(@object, @machine, :shift_up, :idling, :first_gear),
+        StateMachines::Transition.new(@object, @machine, :shift_down, :first_gear, :idling)
+      ]
+    ], @paths
+  end
+end
diff --git a/test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb b/test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb
new file mode 100644
index 0000000..6639b7e
--- /dev/null
+++ b/test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class PathCollectionWithDuplicateNodesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :shift_up do
+      transition parked: :idling, idling: :first_gear
+    end
+    @machine.event :park do
+      transition first_gear: :idling
+    end
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @paths = StateMachines::PathCollection.new(@object, @machine)
+  end
+
+  def test_should_not_include_duplicates_in_from_states
+    assert_equal [:parked, :idling, :first_gear], @paths.from_states
+  end
+
+  def test_should_not_include_duplicates_in_to_states
+    assert_equal [:idling, :first_gear], @paths.to_states
+  end
+
+  def test_should_not_include_duplicates_in_events
+    assert_equal [:shift_up, :park], @paths.events
+  end
+end
diff --git a/test/unit/path_collection/path_collection_with_from_state_test.rb b/test/unit/path_collection/path_collection_with_from_state_test.rb
new file mode 100644
index 0000000..e643dfc
--- /dev/null
+++ b/test/unit/path_collection/path_collection_with_from_state_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class PathCollectionWithFromStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling, :first_gear
+    @machine.event :park do
+      transition idling: :parked
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @paths = StateMachines::PathCollection.new(@object, @machine, from: :idling)
+  end
+
+  def test_should_generate_paths_from_custom_from_state
+    assert_equal [[
+      StateMachines::Transition.new(@object, @machine, :park, :idling, :parked)
+    ]], @paths
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :idling, @paths.from_name
+  end
+end
diff --git a/test/unit/path_collection/path_collection_with_paths_test.rb b/test/unit/path_collection/path_collection_with_paths_test.rb
new file mode 100644
index 0000000..ac7c4ef
--- /dev/null
+++ b/test/unit/path_collection/path_collection_with_paths_test.rb
@@ -0,0 +1,47 @@
+require_relative '../../test_helper'
+
+class PathCollectionWithPathsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling, :first_gear
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :shift_up do
+      transition idling: :first_gear
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @paths = StateMachines::PathCollection.new(@object, @machine)
+  end
+
+  def test_should_enumerate_paths
+    assert_equal [[
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+      StateMachines::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)
+    ]], @paths
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :parked, @paths.from_name
+  end
+
+  def test_should_not_have_a_to_name
+    assert_nil @paths.to_name
+  end
+
+  def test_should_have_from_states
+    assert_equal [:parked, :idling], @paths.from_states
+  end
+
+  def test_should_have_to_states
+    assert_equal [:idling, :first_gear], @paths.to_states
+  end
+
+  def test_should_have_no_events
+    assert_equal [:ignite, :shift_up], @paths.events
+  end
+end
diff --git a/test/unit/path_collection/path_collection_with_to_state_test.rb b/test/unit/path_collection/path_collection_with_to_state_test.rb
new file mode 100644
index 0000000..21d0599
--- /dev/null
+++ b/test/unit/path_collection/path_collection_with_to_state_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class PathCollectionWithToStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+    @machine.event :shift_up do
+      transition parked: :idling, idling: :first_gear
+    end
+    @machine.event :shift_down do
+      transition first_gear: :idling
+    end
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @paths = StateMachines::PathCollection.new(@object, @machine, to: :idling)
+  end
+
+  def test_should_stop_paths_once_target_state_reached
+    assert_equal [
+      [StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)],
+      [StateMachines::Transition.new(@object, @machine, :shift_up, :parked, :idling)]
+    ], @paths
+  end
+end
diff --git a/test/unit/path_collection/path_with_guarded_paths_test.rb b/test/unit/path_collection/path_with_guarded_paths_test.rb
new file mode 100644
index 0000000..98f6824
--- /dev/null
+++ b/test/unit/path_collection/path_with_guarded_paths_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+class PathWithGuardedPathsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling, :first_gear
+    @machine.event :ignite do
+      transition parked: :idling, if: lambda { false }
+    end
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_not_enumerate_paths_if_guard_enabled
+    assert_equal [], StateMachines::PathCollection.new(@object, @machine)
+  end
+
+  def test_should_enumerate_paths_if_guard_disabled
+    paths = StateMachines::PathCollection.new(@object, @machine, guard: false)
+    assert_equal [[
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ]], paths
+  end
+end
diff --git a/test/unit/state/state_after_being_copied_test.rb b/test/unit/state/state_after_being_copied_test.rb
new file mode 100644
index 0000000..e8edfde
--- /dev/null
+++ b/test/unit/state/state_after_being_copied_test.rb
@@ -0,0 +1,19 @@
+require_relative '../../test_helper'
+
+class StateAfterBeingCopiedTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked)
+    @copied_state = @state.dup
+  end
+
+  def test_should_not_have_the_context
+    state_context = nil
+    @state.context { state_context = self }
+
+    copied_state_context = nil
+    @copied_state.context { copied_state_context = self }
+
+    refute_same state_context, copied_state_context
+  end
+end
diff --git a/test/unit/state/state_by_default_test.rb b/test/unit/state/state_by_default_test.rb
new file mode 100644
index 0000000..0fa2e5a
--- /dev/null
+++ b/test/unit/state/state_by_default_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class StateByDefaultTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked)
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @state.machine
+  end
+
+  def test_should_have_a_name
+    assert_equal :parked, @state.name
+  end
+
+  def test_should_have_a_qualified_name
+    assert_equal :parked, @state.qualified_name
+  end
+
+  def test_should_have_a_human_name
+    assert_equal 'parked', @state.human_name
+  end
+
+  def test_should_use_stringify_the_name_as_the_value
+    assert_equal 'parked', @state.value
+  end
+
+  def test_should_not_be_initial
+    refute @state.initial
+  end
+
+  def test_should_not_have_a_matcher
+    assert_nil @state.matcher
+  end
+
+  def test_should_not_have_any_methods
+    expected = {}
+    assert_equal expected, @state.context_methods
+  end
+end
diff --git a/test/unit/state/state_final_test.rb b/test/unit/state/state_final_test.rb
new file mode 100644
index 0000000..12877b3
--- /dev/null
+++ b/test/unit/state/state_final_test.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class StateFinalTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked)
+  end
+
+  def test_should_be_final_without_input_transitions
+    assert @state.final?
+  end
+
+  def test_should_be_final_with_input_transitions
+    @machine.event :park do
+      transition idling: :parked
+    end
+
+    assert @state.final?
+  end
+
+  def test_should_be_final_with_loopback
+    @machine.event :ignite do
+      transition parked: same
+    end
+
+    assert @state.final?
+  end
+end
diff --git a/test/unit/state/state_initial_test.rb b/test/unit/state/state_initial_test.rb
new file mode 100644
index 0000000..ea3486d
--- /dev/null
+++ b/test/unit/state/state_initial_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+
+class StateInitialTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, initial: true)
+  end
+
+  def test_should_be_initial
+    assert @state.initial
+    assert @state.initial?
+  end
+end
diff --git a/test/unit/state/state_not_final_test.rb b/test/unit/state/state_not_final_test.rb
new file mode 100644
index 0000000..cf14f14
--- /dev/null
+++ b/test/unit/state/state_not_final_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class StateNotFinalTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked)
+  end
+
+  def test_should_not_be_final_with_outgoing_whitelist_transitions
+    @machine.event :ignite do
+      transition parked: :idling
+    end
+
+    refute @state.final?
+  end
+
+  def test_should_not_be_final_with_outgoing_all_transitions
+    @machine.event :ignite do
+      transition all => :idling
+    end
+
+    refute @state.final?
+  end
+
+  def test_should_not_be_final_with_outgoing_blacklist_transitions
+    @machine.event :ignite do
+      transition all - :first_gear => :idling
+    end
+
+    refute @state.final?
+  end
+end
diff --git a/test/unit/state/state_not_initial_test.rb b/test/unit/state/state_not_initial_test.rb
new file mode 100644
index 0000000..7ffbebf
--- /dev/null
+++ b/test/unit/state/state_not_initial_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+
+class StateNotInitialTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, initial: false)
+  end
+
+  def test_should_not_be_initial
+    refute @state.initial
+    refute @state.initial?
+  end
+end
diff --git a/test/unit/state/state_test.rb b/test/unit/state/state_test.rb
new file mode 100644
index 0000000..7f8a2d1
--- /dev/null
+++ b/test/unit/state/state_test.rb
@@ -0,0 +1,44 @@
+require_relative '../../test_helper'
+
+class StateTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked)
+  end
+
+  def test_should_raise_exception_if_invalid_option_specified
+    exception = assert_raises(ArgumentError) { StateMachines::State.new(@machine, :parked, invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :initial, :value, :cache, :if, :human_name', exception.message
+  end
+
+  def test_should_allow_changing_machine
+    new_machine = StateMachines::Machine.new(Class.new)
+    @state.machine = new_machine
+    assert_equal new_machine, @state.machine
+  end
+
+  def test_should_allow_changing_value
+    @state.value = 1
+    assert_equal 1, @state.value
+  end
+
+  def test_should_allow_changing_initial
+    @state.initial = true
+    assert @state.initial
+  end
+
+  def test_should_allow_changing_matcher
+    matcher = lambda {}
+    @state.matcher = matcher
+    assert_equal matcher, @state.matcher
+  end
+
+  def test_should_allow_changing_human_name
+    @state.human_name = 'stopped'
+    assert_equal 'stopped', @state.human_name
+  end
+
+  def test_should_use_pretty_inspect
+    assert_equal '#<StateMachines::State name=:parked value="parked" initial=false>', @state.inspect
+  end
+end
diff --git a/test/unit/state/state_with_cached_lambda_value_test.rb b/test/unit/state/state_with_cached_lambda_value_test.rb
new file mode 100644
index 0000000..12dbbec
--- /dev/null
+++ b/test/unit/state/state_with_cached_lambda_value_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class StateWithCachedLambdaValueTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @dynamic_value = -> { 'value' }
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, value: @dynamic_value, cache: true)
+  end
+
+  def test_should_be_caching
+    assert @state.cache
+  end
+
+  def test_should_evaluate_value
+    assert_equal 'value', @state.value
+  end
+
+  def test_should_only_evaluate_value_once
+    value = @state.value
+    assert_same value, @state.value
+  end
+
+  def test_should_update_value_index_for_state_collection
+    @state.value
+    assert_equal @state, @machine.states['value', :value]
+    assert_nil @machine.states[@dynamic_value, :value]
+  end
+end
diff --git a/test/unit/state/state_with_conflicting_helpers_after_definition_test.rb b/test/unit/state/state_with_conflicting_helpers_after_definition_test.rb
new file mode 100644
index 0000000..cd26a9f
--- /dev/null
+++ b/test/unit/state/state_with_conflicting_helpers_after_definition_test.rb
@@ -0,0 +1,38 @@
+require_relative '../../test_helper'
+
+class StateWithConflictingHelpersAfterDefinitionTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @klass = Class.new do
+      def parked?
+        0
+      end
+    end
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+    @object = @klass.new
+  end
+
+  def test_should_not_override_state_predicate
+    assert_equal 0, @object.parked?
+  end
+
+  def test_should_still_allow_super_chaining
+    @klass.class_eval do
+      def parked?
+        super
+      end
+    end
+
+    assert_equal false, @object.parked?
+  end
+
+  def test_should_not_output_warning
+    assert_equal '', $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
diff --git a/test/unit/state/state_with_conflicting_helpers_before_definition_test.rb b/test/unit/state/state_with_conflicting_helpers_before_definition_test.rb
new file mode 100644
index 0000000..686e88b
--- /dev/null
+++ b/test/unit/state/state_with_conflicting_helpers_before_definition_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class StateWithConflictingHelpersBeforeDefinitionTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @superclass = Class.new do
+      def parked?
+        0
+      end
+    end
+    @klass = Class.new(@superclass)
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+    @object = @klass.new
+  end
+
+  def test_should_not_override_state_predicate
+    assert_equal 0, @object.parked?
+  end
+
+  def test_should_output_warning
+    assert_equal "Instance method \"parked?\" is already defined in #{@superclass}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
diff --git a/test/unit/state/state_with_conflicting_machine_name_test.rb b/test/unit/state/state_with_conflicting_machine_name_test.rb
new file mode 100644
index 0000000..2023739
--- /dev/null
+++ b/test/unit/state/state_with_conflicting_machine_name_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+require 'stringio'
+
+class StateWithConflictingMachineNameTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @klass = Class.new
+    @state_machine = StateMachines::Machine.new(@klass, :state)
+  end
+
+  def test_should_output_warning_if_name_conflicts
+    StateMachines::State.new(@state_machine, :state)
+    assert_equal "Instance method \"state?\" is already defined in #{@klass} :state instance helpers, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
diff --git a/test/unit/state/state_with_conflicting_machine_test.rb b/test/unit/state/state_with_conflicting_machine_test.rb
new file mode 100644
index 0000000..11ec068
--- /dev/null
+++ b/test/unit/state/state_with_conflicting_machine_test.rb
@@ -0,0 +1,37 @@
+require_relative '../../test_helper'
+require 'stringio'
+
+class StateWithConflictingMachineTest < StateMachinesTest
+  def setup
+    @original_stderr, $stderr = $stderr, StringIO.new
+
+    @klass = Class.new
+    @state_machine = StateMachines::Machine.new(@klass, :state)
+    @state_machine.states << @state = StateMachines::State.new(@state_machine, :parked)
+  end
+
+  def test_should_output_warning_if_using_different_attribute
+    @status_machine = StateMachines::Machine.new(@klass, :status)
+    @status_machine.states << @state = StateMachines::State.new(@status_machine, :parked)
+
+    assert_equal "State :parked for :status is already defined in :state\n", $stderr.string
+  end
+
+  def test_should_not_output_warning_if_using_same_attribute
+    @status_machine = StateMachines::Machine.new(@klass, :status, attribute: :state)
+    @status_machine.states << @state = StateMachines::State.new(@status_machine, :parked)
+
+    assert_equal '', $stderr.string
+  end
+
+  def test_should_not_output_warning_if_using_different_namespace
+    @status_machine = StateMachines::Machine.new(@klass, :status, namespace: 'alarm')
+    @status_machine.states << @state = StateMachines::State.new(@status_machine, :parked)
+
+    assert_equal '', $stderr.string
+  end
+
+  def teardown
+    $stderr = @original_stderr
+  end
+end
diff --git a/test/unit/state/state_with_context_test.rb b/test/unit/state/state_with_context_test.rb
new file mode 100644
index 0000000..b042386
--- /dev/null
+++ b/test/unit/state/state_with_context_test.rb
@@ -0,0 +1,60 @@
+require_relative '../../test_helper'
+
+class StateWithContextTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @ancestors = @klass.ancestors
+    @machine.states << @state = StateMachines::State.new(@machine, :idling)
+
+    context = nil
+    speed_method = nil
+    rpm_method = nil
+    @result = @state.context do
+      context = self
+
+      def speed
+        0
+      end
+
+      speed_method = instance_method(:speed)
+
+      def rpm
+        1000
+      end
+
+      rpm_method = instance_method(:rpm)
+    end
+
+    @context = context
+    @speed_method = speed_method
+    @rpm_method = rpm_method
+  end
+
+  def test_should_return_true
+    assert_equal true, @result
+  end
+
+  def test_should_include_new_module_in_owner_class
+    refute_equal @ancestors, @klass.ancestors
+    assert_equal [@context], @klass.ancestors - @ancestors
+  end
+
+  def test_should_define_each_context_method_in_owner_class
+    %w(speed rpm).each { |method| assert @klass.method_defined?(method) }
+  end
+
+  def test_should_define_aliased_context_method_in_owner_class
+    %w(speed rpm).each { |method| assert @klass.method_defined?("__state_idling_#{method}_#{@context.object_id}__") }
+  end
+
+  def test_should_not_use_context_methods_as_owner_class_methods
+    refute_equal @speed_method, @state.context_methods[:speed]
+    refute_equal @rpm_method, @state.context_methods[:rpm]
+  end
+
+  def test_should_use_context_methods_as_aliased_owner_class_methods
+    assert_equal @speed_method, @state.context_methods[:"__state_idling_speed_#{@context.object_id}__"]
+    assert_equal @rpm_method, @state.context_methods[:"__state_idling_rpm_#{@context.object_id}__"]
+  end
+end
diff --git a/test/unit/state/state_with_dynamic_human_name_test.rb b/test/unit/state/state_with_dynamic_human_name_test.rb
new file mode 100644
index 0000000..d9e7298
--- /dev/null
+++ b/test/unit/state/state_with_dynamic_human_name_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class StateWithDynamicHumanNameTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, human_name: lambda { |_state, object| ['stopped', object] })
+  end
+
+  def test_should_use_custom_human_name
+    human_name, klass = @state.human_name
+    assert_equal 'stopped', human_name
+    assert_equal @klass, klass
+  end
+
+  def test_should_allow_custom_class_to_be_passed_through
+    human_name, klass = @state.human_name(1)
+    assert_equal 'stopped', human_name
+    assert_equal 1, klass
+  end
+
+  def test_should_not_cache_value
+    refute_same @state.human_name, @state.human_name
+  end
+end
diff --git a/test/unit/state/state_with_existing_context_method_test.rb b/test/unit/state/state_with_existing_context_method_test.rb
new file mode 100644
index 0000000..6caad15
--- /dev/null
+++ b/test/unit/state/state_with_existing_context_method_test.rb
@@ -0,0 +1,24 @@
+require_relative '../../test_helper'
+
+class StateWithExistingContextMethodTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def speed
+        60
+      end
+    end
+    @original_speed_method = @klass.instance_method(:speed)
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :idling)
+    @state.context do
+      def speed
+        0
+      end
+    end
+  end
+
+  def test_should_not_override_method
+    assert_equal @original_speed_method, @klass.instance_method(:speed)
+  end
+end
diff --git a/test/unit/state/state_with_human_name_test.rb b/test/unit/state/state_with_human_name_test.rb
new file mode 100644
index 0000000..7c0f32d
--- /dev/null
+++ b/test/unit/state/state_with_human_name_test.rb
@@ -0,0 +1,13 @@
+require_relative '../../test_helper'
+
+class StateWithHumanNameTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, human_name: 'stopped')
+  end
+
+  def test_should_use_custom_human_name
+    assert_equal 'stopped', @state.human_name
+  end
+end
diff --git a/test/unit/state/state_with_integer_value_test.rb b/test/unit/state/state_with_integer_value_test.rb
new file mode 100644
index 0000000..01afadf
--- /dev/null
+++ b/test/unit/state/state_with_integer_value_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class StateWithIntegerValueTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, value: 1)
+  end
+
+  def test_should_use_custom_value
+    assert_equal 1, @state.value
+  end
+
+  def test_should_include_value_in_description
+    assert_equal 'parked (1)', @state.description
+  end
+
+  def test_should_allow_human_name_in_description
+    @state.human_name = 'Parked'
+    assert_equal 'Parked (1)', @state.description(human_name: true)
+  end
+
+  def test_should_match_integer_value
+    assert @state.matches?(1)
+    refute @state.matches?(2)
+  end
+
+  def test_should_define_predicate
+    object = @klass.new
+    assert object.respond_to?(:parked?)
+  end
+end
diff --git a/test/unit/state/state_with_invalid_method_call_test.rb b/test/unit/state/state_with_invalid_method_call_test.rb
new file mode 100644
index 0000000..2dc319f
--- /dev/null
+++ b/test/unit/state/state_with_invalid_method_call_test.rb
@@ -0,0 +1,21 @@
+require_relative '../../test_helper'
+
+class StateWithInvalidMethodCallTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @ancestors = @klass.ancestors
+    @machine.states << @state = StateMachines::State.new(@machine, :idling)
+    @state.context do
+      def speed
+        0
+      end
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_call_method_missing_arg
+    assert_equal 1, @state.call(@object, :invalid, method_missing: -> { 1 })
+  end
+end
diff --git a/test/unit/state/state_with_lambda_value_test.rb b/test/unit/state/state_with_lambda_value_test.rb
new file mode 100644
index 0000000..2b4109e
--- /dev/null
+++ b/test/unit/state/state_with_lambda_value_test.rb
@@ -0,0 +1,37 @@
+require_relative '../../test_helper'
+
+class StateWithLambdaValueTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @args = nil
+    @machine = StateMachines::Machine.new(@klass)
+    @value = ->(*args) { @args = args; :parked }
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, value: @value)
+  end
+
+  def test_should_use_evaluated_value_by_default
+    assert_equal :parked, @state.value
+  end
+
+  def test_should_allow_access_to_original_value
+    assert_equal @value, @state.value(false)
+  end
+
+  def test_should_include_masked_value_in_description
+    assert_equal 'parked (*)', @state.description
+  end
+
+  def test_should_not_pass_in_any_arguments
+    @state.value
+    assert_equal [], @args
+  end
+
+  def test_should_define_predicate
+    object = @klass.new
+    assert object.respond_to?(:parked?)
+  end
+
+  def test_should_match_evaluated_value
+    assert @state.matches?(:parked)
+  end
+end
diff --git a/test/unit/state/state_with_matcher_test.rb b/test/unit/state/state_with_matcher_test.rb
new file mode 100644
index 0000000..ed74146
--- /dev/null
+++ b/test/unit/state/state_with_matcher_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class StateWithMatcherTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @args = nil
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, if: lambda { |value| value == 1 })
+  end
+
+  def test_should_not_match_actual_value
+    refute @state.matches?('parked')
+  end
+
+  def test_should_match_evaluated_block
+    assert @state.matches?(1)
+  end
+end
diff --git a/test/unit/state/state_with_multiple_contexts_test.rb b/test/unit/state/state_with_multiple_contexts_test.rb
new file mode 100644
index 0000000..3cb0e46
--- /dev/null
+++ b/test/unit/state/state_with_multiple_contexts_test.rb
@@ -0,0 +1,57 @@
+require_relative '../../test_helper'
+
+class StateWithMultipleContextsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @ancestors = @klass.ancestors
+    @machine.states << @state = StateMachines::State.new(@machine, :idling)
+
+    context = nil
+    speed_method = nil
+    @state.context do
+      context = self
+
+      def speed
+        0
+      end
+
+      speed_method = instance_method(:speed)
+    end
+    @context = context
+    @speed_method = speed_method
+
+    rpm_method = nil
+    @state.context do
+      def rpm
+        1000
+      end
+
+      rpm_method = instance_method(:rpm)
+    end
+    @rpm_method = rpm_method
+  end
+
+  def test_should_include_new_module_in_owner_class
+    refute_equal @ancestors, @klass.ancestors
+    assert_equal [@context], @klass.ancestors - @ancestors
+  end
+
+  def test_should_define_each_context_method_in_owner_class
+    %w(speed rpm).each { |method| assert @klass.method_defined?(method) }
+  end
+
+  def test_should_define_aliased_context_method_in_owner_class
+    %w(speed rpm).each { |method| assert @klass.method_defined?("__state_idling_#{method}_#{@context.object_id}__") }
+  end
+
+  def test_should_not_use_context_methods_as_owner_class_methods
+    refute_equal @speed_method, @state.context_methods[:speed]
+    refute_equal @rpm_method, @state.context_methods[:rpm]
+  end
+
+  def test_should_use_context_methods_as_aliased_owner_class_methods
+    assert_equal @speed_method, @state.context_methods[:"__state_idling_speed_#{@context.object_id}__"]
+    assert_equal @rpm_method, @state.context_methods[:"__state_idling_rpm_#{@context.object_id}__"]
+  end
+end
diff --git a/test/unit/state/state_with_name_test.rb b/test/unit/state/state_with_name_test.rb
new file mode 100644
index 0000000..1a1dba6
--- /dev/null
+++ b/test/unit/state/state_with_name_test.rb
@@ -0,0 +1,43 @@
+require_relative '../../test_helper'
+
+class StateWithNameTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked)
+  end
+
+  def test_should_have_a_name
+    assert_equal :parked, @state.name
+  end
+
+  def test_should_have_a_qualified_name
+    assert_equal :parked, @state.name
+  end
+
+  def test_should_have_a_human_name
+    assert_equal 'parked', @state.human_name
+  end
+
+  def test_should_use_stringify_the_name_as_the_value
+    assert_equal 'parked', @state.value
+  end
+
+  def test_should_match_stringified_name
+    assert @state.matches?('parked')
+    refute @state.matches?('idling')
+  end
+
+  def test_should_not_include_value_in_description
+    assert_equal 'parked', @state.description
+  end
+
+  def test_should_allow_using_human_name_in_description
+    @state.human_name = 'Parked'
+    assert_equal 'Parked', @state.description(human_name: true)
+  end
+
+  def test_should_define_predicate
+    assert @klass.new.respond_to?(:parked?)
+  end
+end
diff --git a/test/unit/state/state_with_namespace_test.rb b/test/unit/state/state_with_namespace_test.rb
new file mode 100644
index 0000000..7fc53a5
--- /dev/null
+++ b/test/unit/state/state_with_namespace_test.rb
@@ -0,0 +1,22 @@
+require_relative '../../test_helper'
+
+class StateWithNamespaceTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, namespace: 'alarm')
+    @machine.states << @state = StateMachines::State.new(@machine, :active)
+    @object = @klass.new
+  end
+
+  def test_should_have_a_name
+    assert_equal :active, @state.name
+  end
+
+  def test_should_have_a_qualified_name
+    assert_equal :alarm_active, @state.qualified_name
+  end
+
+  def test_should_namespace_predicate
+    assert @object.respond_to?(:alarm_active?)
+  end
+end
diff --git a/test/unit/state/state_with_nil_value_test.rb b/test/unit/state/state_with_nil_value_test.rb
new file mode 100644
index 0000000..2dcc4c6
--- /dev/null
+++ b/test/unit/state/state_with_nil_value_test.rb
@@ -0,0 +1,35 @@
+require_relative '../../test_helper'
+
+class StateWithNilValueTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, value: nil)
+  end
+
+  def test_should_have_a_name
+    assert_equal :parked, @state.name
+  end
+
+  def test_should_have_a_nil_value
+    assert_nil @state.value
+  end
+
+  def test_should_match_nil_values
+    assert @state.matches?(nil)
+  end
+
+  def test_should_have_a_description
+    assert_equal 'parked (nil)', @state.description
+  end
+
+  def test_should_have_a_description_with_human_name
+    @state.human_name = 'Parked'
+    assert_equal 'Parked (nil)', @state.description(human_name: true)
+  end
+
+  def test_should_define_predicate
+    object = @klass.new
+    assert object.respond_to?(:parked?)
+  end
+end
diff --git a/test/unit/state/state_with_redefined_context_method_test.rb b/test/unit/state/state_with_redefined_context_method_test.rb
new file mode 100644
index 0000000..53a526c
--- /dev/null
+++ b/test/unit/state/state_with_redefined_context_method_test.rb
@@ -0,0 +1,45 @@
+require_relative '../../test_helper'
+
+class StateWithRedefinedContextMethodTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, 'on')
+
+    old_context = nil
+    old_speed_method = nil
+    @state.context do
+      old_context = self
+
+      def speed
+        0
+      end
+
+      old_speed_method = instance_method(:speed)
+    end
+    @old_context = old_context
+    @old_speed_method = old_speed_method
+
+    current_context = nil
+    current_speed_method = nil
+    @state.context do
+      current_context = self
+
+      def speed
+        'green'
+      end
+
+      current_speed_method = instance_method(:speed)
+    end
+    @current_context = current_context
+    @current_speed_method = current_speed_method
+  end
+
+  def test_should_track_latest_defined_method
+    assert_equal @current_speed_method, @state.context_methods[:"__state_on_speed_#{@current_context.object_id}__"]
+  end
+
+  def test_should_have_the_same_context
+    assert_equal @current_context, @old_context
+  end
+end
diff --git a/test/unit/state/state_with_symbolic_value_test.rb b/test/unit/state/state_with_symbolic_value_test.rb
new file mode 100644
index 0000000..eb747fc
--- /dev/null
+++ b/test/unit/state/state_with_symbolic_value_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class StateWithSymbolicValueTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, value: :parked)
+  end
+
+  def test_should_use_custom_value
+    assert_equal :parked, @state.value
+  end
+
+  def test_should_not_include_value_in_description
+    assert_equal 'parked', @state.description
+  end
+
+  def test_should_allow_human_name_in_description
+    @state.human_name = 'Parked'
+    assert_equal 'Parked', @state.description(human_name: true)
+  end
+
+  def test_should_match_symbolic_value
+    assert @state.matches?(:parked)
+    refute @state.matches?('parked')
+  end
+
+  def test_should_define_predicate
+    object = @klass.new
+    assert object.respond_to?(:parked?)
+  end
+end
diff --git a/test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb b/test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb
new file mode 100644
index 0000000..3f474d1
--- /dev/null
+++ b/test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class StateWithValidInheritedMethodCallForCurrentStateTest < StateMachinesTest
+  def setup
+    @superclass = Class.new do
+      def speed(arg = nil)
+        [arg]
+      end
+    end
+    @klass = Class.new(@superclass)
+    @machine = StateMachines::Machine.new(@klass, initial: :idling)
+    @ancestors = @klass.ancestors
+    @state = @machine.state(:idling)
+    @state.context do
+      def speed(arg = nil)
+        [arg] + super(2)
+      end
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_not_raise_an_exception
+    @state.call(@object, :speed, method_missing: lambda { fail })
+  end
+
+  def test_should_be_able_to_call_super
+    assert_equal [1, 2], @state.call(@object, :speed, 1)
+  end
+
+  def test_should_allow_redefinition
+    @state.context do
+      def speed(arg = nil)
+        [arg] + super(3)
+      end
+    end
+
+    assert_equal [1, 3], @state.call(@object, :speed, 1)
+  end
+end
diff --git a/test/unit/state/state_with_valid_method_call_for_current_state_test.rb b/test/unit/state/state_with_valid_method_call_for_current_state_test.rb
new file mode 100644
index 0000000..eb0febb
--- /dev/null
+++ b/test/unit/state/state_with_valid_method_call_for_current_state_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class StateWithValidMethodCallForCurrentStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :idling)
+    @ancestors = @klass.ancestors
+    @state = @machine.state(:idling)
+    @state.context do
+      def speed(arg = nil)
+        block_given? ? [arg, yield] : arg
+      end
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_not_raise_an_exception
+    @state.call(@object, :speed, method_missing: lambda { fail })
+  end
+
+  def test_should_pass_arguments_through
+    assert_equal 1, @state.call(@object, :speed, 1, method_missing: lambda {})
+  end
+
+  def test_should_pass_blocks_through
+    assert_equal [nil, 1], @state.call(@object, :speed) { 1 }
+  end
+
+  def test_should_pass_both_arguments_and_blocks_through
+    assert_equal [1, 2], @state.call(@object, :speed, 1, method_missing: lambda {}) { 2 }
+  end
+end
diff --git a/test/unit/state/state_with_valid_method_call_for_different_state_test.rb b/test/unit/state/state_with_valid_method_call_for_different_state_test.rb
new file mode 100644
index 0000000..08da917
--- /dev/null
+++ b/test/unit/state/state_with_valid_method_call_for_different_state_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class StateWithValidMethodCallForDifferentStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @ancestors = @klass.ancestors
+    @machine.states << @state = StateMachines::State.new(@machine, :idling)
+    @state.context do
+      def speed
+        0
+      end
+    end
+
+    @object = @klass.new
+  end
+
+  def test_should_call_method_missing_arg
+    assert_equal 1, @state.call(@object, :speed, method_missing: lambda { 1 })
+  end
+
+  def test_should_raise_invalid_context_on_no_method_error
+    exception = assert_raises(StateMachines::InvalidContext) do
+      @state.call(@object, :speed, method_missing: lambda { fail NoMethodError.new('Invalid', :speed, []) })
+    end
+    assert_equal @object, exception.object
+    assert_equal 'State nil for :state is not a valid context for calling #speed', exception.message
+  end
+
+  def test_should_raise_original_error_on_no_method_error_with_different_arguments
+    assert_raises(NoMethodError) do
+      @state.call(@object, :speed, method_missing: lambda { fail NoMethodError.new('Invalid', :speed, [1]) })
+    end
+  end
+
+  def test_should_raise_original_error_on_no_method_error_for_different_method
+    assert_raises(NoMethodError) do
+      @state.call(@object, :speed, method_missing: lambda { fail NoMethodError.new('Invalid', :rpm, []) })
+    end
+  end
+end
diff --git a/test/unit/state/state_without_cached_lambda_value_test.rb b/test/unit/state/state_without_cached_lambda_value_test.rb
new file mode 100644
index 0000000..75a2591
--- /dev/null
+++ b/test/unit/state/state_without_cached_lambda_value_test.rb
@@ -0,0 +1,25 @@
+require_relative '../../test_helper'
+
+class StateWithoutCachedLambdaValueTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @dynamic_value = -> { 'value' }
+    @machine.states << @state = StateMachines::State.new(@machine, :parked, value: @dynamic_value)
+  end
+
+  def test_should_not_be_caching
+    refute @state.cache
+  end
+
+  def test_should_evaluate_value_each_time
+    value = @state.value
+    refute_same value, @state.value
+  end
+
+  def test_should_not_update_value_index_for_state_collection
+    @state.value
+    assert_nil @machine.states['value', :value]
+    assert_equal @state, @machine.states[@dynamic_value, :value]
+  end
+end
diff --git a/test/unit/state/state_without_name_test.rb b/test/unit/state/state_without_name_test.rb
new file mode 100644
index 0000000..33e9b99
--- /dev/null
+++ b/test/unit/state/state_without_name_test.rb
@@ -0,0 +1,39 @@
+require_relative '../../test_helper'
+
+class StateWithoutNameTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.states << @state = StateMachines::State.new(@machine, nil)
+  end
+
+  def test_should_have_a_nil_name
+    assert_nil @state.name
+  end
+
+  def test_should_have_a_nil_qualified_name
+    assert_nil @state.qualified_name
+  end
+
+  def test_should_have_an_empty_human_name
+    assert_equal 'nil', @state.human_name
+  end
+
+  def test_should_have_a_nil_value
+    assert_nil @state.value
+  end
+
+  def test_should_not_redefine_nil_predicate
+    object = @klass.new
+    refute object.nil?
+    refute object.respond_to?('?')
+  end
+
+  def test_should_have_a_description
+    assert_equal 'nil', @state.description
+  end
+
+  def test_should_have_a_description_using_human_name
+    assert_equal 'nil', @state.description(human_name: true)
+  end
+end
diff --git a/test/unit/state_collection/state_collection_by_default_test.rb b/test/unit/state_collection/state_collection_by_default_test.rb
new file mode 100644
index 0000000..f1f4ea7
--- /dev/null
+++ b/test/unit/state_collection/state_collection_by_default_test.rb
@@ -0,0 +1,21 @@
+require_relative '../../test_helper'
+
+class StateCollectionByDefaultTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @states = StateMachines::StateCollection.new(@machine)
+  end
+
+  def test_should_not_have_any_nodes
+    assert_equal 0, @states.length
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @states.machine
+  end
+
+  def test_should_be_empty_by_priority
+    assert_equal [], @states.by_priority
+  end
+end
+
diff --git a/test/unit/state_collection/state_collection_string_test.rb b/test/unit/state_collection/state_collection_string_test.rb
new file mode 100644
index 0000000..86d0546
--- /dev/null
+++ b/test/unit/state_collection/state_collection_string_test.rb
@@ -0,0 +1,35 @@
+require_relative '../../test_helper'
+
+class StateCollectionStringTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @nil = StateMachines::State.new(@machine, nil)
+    @states << @parked = StateMachines::State.new(@machine, 'parked')
+    @machine.states.concat(@states)
+
+    @object = @klass.new
+  end
+
+  def test_should_index_by_name
+    assert_equal @parked, @states['parked', :name]
+  end
+
+  def test_should_index_by_name_by_default
+    assert_equal @parked, @states['parked']
+  end
+
+  def test_should_index_by_symbol_name
+    assert_equal @parked, @states[:parked]
+  end
+
+  def test_should_index_by_qualified_name
+    assert_equal @parked, @states['parked', :qualified_name]
+  end
+
+  def test_should_index_by_symbol_qualified_name
+    assert_equal @parked, @states[:parked, :qualified_name]
+  end
+end
diff --git a/test/unit/state_collection/state_collection_test.rb b/test/unit/state_collection/state_collection_test.rb
new file mode 100644
index 0000000..f93ff34
--- /dev/null
+++ b/test/unit/state_collection/state_collection_test.rb
@@ -0,0 +1,74 @@
+require_relative '../../test_helper'
+
+class StateCollectionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @nil = StateMachines::State.new(@machine, nil)
+    @states << @parked = StateMachines::State.new(@machine, :parked)
+    @states << @idling = StateMachines::State.new(@machine, :idling)
+    @machine.states.concat(@states)
+
+    @object = @klass.new
+  end
+
+  def test_should_index_by_name
+    assert_equal @parked, @states[:parked, :name]
+  end
+
+  def test_should_index_by_name_by_default
+    assert_equal @parked, @states[:parked]
+  end
+
+  def test_should_index_by_string_name
+    assert_equal @parked, @states['parked']
+  end
+
+  def test_should_index_by_qualified_name
+    assert_equal @parked, @states[:parked, :qualified_name]
+  end
+
+  def test_should_index_by_string_qualified_name
+    assert_equal @parked, @states['parked', :qualified_name]
+  end
+
+  def test_should_index_by_value
+    assert_equal @parked, @states['parked', :value]
+  end
+
+  def test_should_not_match_if_value_does_not_match
+    refute @states.matches?(@object, :parked)
+    refute @states.matches?(@object, :idling)
+  end
+
+  def test_should_match_if_value_matches
+    assert @states.matches?(@object, nil)
+  end
+
+  def test_raise_exception_if_matching_invalid_state
+    assert_raises(IndexError) { @states.matches?(@object, :invalid) }
+  end
+
+  def test_should_find_state_for_object_if_value_is_known
+    @object.state = 'parked'
+    assert_equal @parked, @states.match(@object)
+  end
+
+  def test_should_find_bang_state_for_object_if_value_is_known
+    @object.state = 'parked'
+    assert_equal @parked, @states.match!(@object)
+  end
+
+  def test_should_not_find_state_for_object_with_unknown_value
+    @object.state = 'invalid'
+    assert_nil @states.match(@object)
+  end
+
+  def test_should_raise_exception_if_finding_bang_state_for_object_with_unknown_value
+    @object.state = 'invalid'
+    exception = assert_raises(ArgumentError) { @states.match!(@object) }
+    assert_equal '"invalid" is not a known state value', exception.message
+  end
+end
diff --git a/test/unit/state_collection/state_collection_with_custom_state_values_test.rb b/test/unit/state_collection/state_collection_with_custom_state_values_test.rb
new file mode 100644
index 0000000..ce6eda3
--- /dev/null
+++ b/test/unit/state_collection/state_collection_with_custom_state_values_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class StateCollectionWithCustomStateValuesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @state = StateMachines::State.new(@machine, :parked, value: 1)
+    @machine.states.concat(@states)
+
+    @object = @klass.new
+    @object.state = 1
+  end
+
+  def test_should_match_if_value_matches
+    assert @states.matches?(@object, :parked)
+  end
+
+  def test_should_not_match_if_value_does_not_match
+    @object.state = 2
+    refute @states.matches?(@object, :parked)
+  end
+
+  def test_should_find_state_for_object_if_value_is_known
+    assert_equal @state, @states.match(@object)
+  end
+end
+
diff --git a/test/unit/state_collection/state_collection_with_event_transitions_test.rb b/test/unit/state_collection/state_collection_with_event_transitions_test.rb
new file mode 100644
index 0000000..611c6e2
--- /dev/null
+++ b/test/unit/state_collection/state_collection_with_event_transitions_test.rb
@@ -0,0 +1,39 @@
+require_relative '../../test_helper'
+
+class StateCollectionWithEventTransitionsTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @parked = StateMachines::State.new(@machine, :parked)
+    @states << @idling = StateMachines::State.new(@machine, :idling)
+    @machine.states.concat(@states)
+
+    @machine.event :ignite do
+      transition to: :idling
+    end
+  end
+
+  def test_should_order_states_after_initial_state
+    @parked.initial = true
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_states_before_states_with_behaviors
+    @parked.context do
+      def speed
+        0
+      end
+    end
+    assert_equal [@idling, @parked], @states.by_priority
+  end
+
+  def test_should_order_states_before_other_states
+    assert_equal [@idling, @parked], @states.by_priority
+  end
+
+  def test_should_order_state_before_callback_states
+    @machine.before_transition from: :parked, do: lambda {}
+    assert_equal [@idling, @parked], @states.by_priority
+  end
+end
diff --git a/test/unit/state_collection/state_collection_with_initial_state_test.rb b/test/unit/state_collection/state_collection_with_initial_state_test.rb
new file mode 100644
index 0000000..aeb177a
--- /dev/null
+++ b/test/unit/state_collection/state_collection_with_initial_state_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class StateCollectionWithInitialStateTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @parked = StateMachines::State.new(@machine, :parked)
+    @states << @idling = StateMachines::State.new(@machine, :idling)
+    @machine.states.concat(@states)
+
+    @parked.initial = true
+  end
+
+  def test_should_order_state_before_transition_states
+    @machine.event :ignite do
+      transition to: :idling
+    end
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_state_before_states_with_behaviors
+    @idling.context do
+      def speed
+        0
+      end
+    end
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_state_before_other_states
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_state_before_callback_states
+    @machine.before_transition from: :idling, do: lambda {}
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+end
+
diff --git a/test/unit/state_collection/state_collection_with_namespace_test.rb b/test/unit/state_collection/state_collection_with_namespace_test.rb
new file mode 100644
index 0000000..304a25d
--- /dev/null
+++ b/test/unit/state_collection/state_collection_with_namespace_test.rb
@@ -0,0 +1,21 @@
+require_relative '../../test_helper'
+
+class StateCollectionWithNamespaceTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, namespace: 'vehicle')
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @state = StateMachines::State.new(@machine, :parked)
+    @machine.states.concat(@states)
+  end
+
+  def test_should_index_by_name
+    assert_equal @state, @states[:parked, :name]
+  end
+
+  def test_should_index_by_qualified_name
+    assert_equal @state, @states[:vehicle_parked, :qualified_name]
+  end
+end
+
diff --git a/test/unit/state_collection/state_collection_with_state_behaviors_test.rb b/test/unit/state_collection/state_collection_with_state_behaviors_test.rb
new file mode 100644
index 0000000..16f9830
--- /dev/null
+++ b/test/unit/state_collection/state_collection_with_state_behaviors_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class StateCollectionWithStateBehaviorsTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @parked = StateMachines::State.new(@machine, :parked)
+    @states << @idling = StateMachines::State.new(@machine, :idling)
+    @machine.states.concat(@states)
+
+    @idling.context do
+      def speed
+        0
+      end
+    end
+  end
+
+  def test_should_order_states_after_initial_state
+    @parked.initial = true
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_states_after_transition_states
+    @machine.event :ignite do
+      transition from: :parked
+    end
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_states_before_other_states
+    assert_equal [@idling, @parked], @states.by_priority
+  end
+
+  def test_should_order_state_before_callback_states
+    @machine.before_transition from: :parked, do: lambda {}
+    assert_equal [@idling, @parked], @states.by_priority
+  end
+end
+
diff --git a/test/unit/state_collection/state_collection_with_state_matchers_test.rb b/test/unit/state_collection/state_collection_with_state_matchers_test.rb
new file mode 100644
index 0000000..41ccc94
--- /dev/null
+++ b/test/unit/state_collection/state_collection_with_state_matchers_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class StateCollectionWithStateMatchersTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @state = StateMachines::State.new(@machine, :parked, if: lambda { |value| !value.nil? })
+    @machine.states.concat(@states)
+
+    @object = @klass.new
+    @object.state = 1
+  end
+
+  def test_should_match_if_value_matches
+    assert @states.matches?(@object, :parked)
+  end
+
+  def test_should_not_match_if_value_does_not_match
+    @object.state = nil
+    refute @states.matches?(@object, :parked)
+  end
+
+  def test_should_find_state_for_object_if_value_is_known
+    assert_equal @state, @states.match(@object)
+  end
+end
+
diff --git a/test/unit/state_collection/state_collection_with_transition_callbacks_test.rb b/test/unit/state_collection/state_collection_with_transition_callbacks_test.rb
new file mode 100644
index 0000000..d112f3b
--- /dev/null
+++ b/test/unit/state_collection/state_collection_with_transition_callbacks_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class StateCollectionWithTransitionCallbacksTest < StateMachinesTest
+  def setup
+    @machine = StateMachines::Machine.new(Class.new)
+    @states = StateMachines::StateCollection.new(@machine)
+
+    @states << @parked = StateMachines::State.new(@machine, :parked)
+    @states << @idling = StateMachines::State.new(@machine, :idling)
+    @machine.states.concat(@states)
+
+    @machine.before_transition to: :idling, do: lambda {}
+  end
+
+  def test_should_order_states_after_initial_state
+    @parked.initial = true
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_states_after_transition_states
+    @machine.event :ignite do
+      transition from: :parked
+    end
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_states_after_states_with_behaviors
+    @parked.context do
+      def speed
+        0
+      end
+    end
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+
+  def test_should_order_states_after_other_states
+    assert_equal [@parked, @idling], @states.by_priority
+  end
+end
+
diff --git a/test/unit/state_context/state_context_proxy_test.rb b/test/unit/state_context/state_context_proxy_test.rb
new file mode 100644
index 0000000..6075839
--- /dev/null
+++ b/test/unit/state_context/state_context_proxy_test.rb
@@ -0,0 +1,26 @@
+require_relative '../../test_helper'
+
+class StateContextProxyTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    machine = StateMachines::Machine.new(@klass, initial: :parked)
+    state = machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(state)
+  end
+
+  def test_should_call_class_with_same_arguments
+    options = {}
+    validation = @state_context.validate(:name, options)
+
+    assert_equal [:name, options], validation
+  end
+
+  def test_should_pass_block_through_to_class
+    options = {}
+    proxy_block = lambda {}
+    validation = @state_context.validate(:name, options, &proxy_block)
+
+    assert_equal [:name, options, proxy_block], validation
+  end
+end
diff --git a/test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb b/test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb
new file mode 100644
index 0000000..16cf08e
--- /dev/null
+++ b/test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb
@@ -0,0 +1,42 @@
+require_relative '../../test_helper'
+
+class StateContextProxyWithIfAndUnlessConditionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    machine = StateMachines::Machine.new(@klass, initial: :parked)
+    state = machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(state)
+    @object = @klass.new
+
+    @if_condition_result = nil
+    @unless_condition_result = nil
+    @options = @state_context.validate(if: lambda { @if_condition_result }, unless: lambda { @unless_condition_result })[0]
+  end
+
+  def test_should_be_false_if_if_condition_is_false
+    @if_condition_result = false
+    @unless_condition_result = false
+    refute @options[:if].call(@object)
+
+    @if_condition_result = false
+    @unless_condition_result = true
+    refute @options[:if].call(@object)
+  end
+
+  def test_should_be_false_if_unless_condition_is_true
+    @if_condition_result = false
+    @unless_condition_result = true
+    refute @options[:if].call(@object)
+
+    @if_condition_result = true
+    @unless_condition_result = true
+    refute @options[:if].call(@object)
+  end
+
+  def test_should_be_true_if_if_condition_is_true_and_unless_condition_is_false
+    @if_condition_result = true
+    @unless_condition_result = false
+    assert @options[:if].call(@object)
+  end
+end
diff --git a/test/unit/state_context/state_context_proxy_with_if_condition_test.rb b/test/unit/state_context/state_context_proxy_with_if_condition_test.rb
new file mode 100644
index 0000000..ad6bbc3
--- /dev/null
+++ b/test/unit/state_context/state_context_proxy_with_if_condition_test.rb
@@ -0,0 +1,64 @@
+require_relative '../../test_helper'
+
+class StateContextProxyWithIfConditionTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    machine = StateMachines::Machine.new(@klass, initial: :parked)
+    state = machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(state)
+    @object = @klass.new
+
+    @condition_result = nil
+    @options = @state_context.validate(if: lambda { @condition_result })[0]
+  end
+
+  def test_should_have_if_option
+    refute_nil @options[:if]
+  end
+
+  def test_should_be_false_if_state_is_different
+    @object.state = nil
+    refute @options[:if].call(@object)
+  end
+
+  def test_should_be_false_if_original_condition_is_false
+    @condition_result = false
+    refute @options[:if].call(@object)
+  end
+
+  def test_should_be_true_if_state_matches_and_original_condition_is_true
+    @condition_result = true
+    assert @options[:if].call(@object)
+  end
+
+  def test_should_evaluate_symbol_condition
+    @klass.class_eval do
+      attr_accessor :callback
+    end
+
+    options = @state_context.validate(if: :callback)[0]
+
+    object = @klass.new
+    object.callback = false
+    refute options[:if].call(object)
+
+    object.callback = true
+    assert options[:if].call(object)
+  end
+
+  def test_should_evaluate_string_condition
+    @klass.class_eval do
+      attr_accessor :callback
+    end
+
+    options = @state_context.validate(if: '@callback')[0]
+
+    object = @klass.new
+    object.callback = false
+    refute options[:if].call(object)
+
+    object.callback = true
+    assert options[:if].call(object)
+  end
+end
diff --git a/test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb b/test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb
new file mode 100644
index 0000000..16749a1
--- /dev/null
+++ b/test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class StateContextProxyWithMultipleIfConditionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    machine = StateMachines::Machine.new(@klass, initial: :parked)
+    state = machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(state)
+    @object = @klass.new
+
+    @first_condition_result = nil
+    @second_condition_result = nil
+    @options = @state_context.validate(if: [lambda { @first_condition_result }, lambda { @second_condition_result }])[0]
+  end
+
+  def test_should_be_true_if_all_conditions_are_true
+    @first_condition_result = true
+    @second_condition_result = true
+    assert @options[:if].call(@object)
+  end
+
+  def test_should_be_false_if_any_condition_is_false
+    @first_condition_result = true
+    @second_condition_result = false
+    refute @options[:if].call(@object)
+
+    @first_condition_result = false
+    @second_condition_result = true
+    refute @options[:if].call(@object)
+  end
+end
diff --git a/test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb b/test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb
new file mode 100644
index 0000000..47255c4
--- /dev/null
+++ b/test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class StateContextProxyWithMultipleUnlessConditionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    machine = StateMachines::Machine.new(@klass, initial: :parked)
+    state = machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(state)
+    @object = @klass.new
+
+    @first_condition_result = nil
+    @second_condition_result = nil
+    @options = @state_context.validate(unless: [-> { @first_condition_result }, lambda { @second_condition_result }])[0]
+  end
+
+  def test_should_be_true_if_all_conditions_are_false
+    @first_condition_result = false
+    @second_condition_result = false
+    assert @options[:if].call(@object)
+  end
+
+  def test_should_be_false_if_any_condition_is_true
+    @first_condition_result = true
+    @second_condition_result = false
+    refute @options[:if].call(@object)
+
+    @first_condition_result = false
+    @second_condition_result = true
+    refute @options[:if].call(@object)
+  end
+end
diff --git a/test/unit/state_context/state_context_proxy_with_unless_condition_test.rb b/test/unit/state_context/state_context_proxy_with_unless_condition_test.rb
new file mode 100644
index 0000000..780c9e4
--- /dev/null
+++ b/test/unit/state_context/state_context_proxy_with_unless_condition_test.rb
@@ -0,0 +1,64 @@
+require_relative '../../test_helper'
+
+class StateContextProxyWithUnlessConditionTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    machine = StateMachines::Machine.new(@klass, initial: :parked)
+    state = machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(state)
+    @object = @klass.new
+
+    @condition_result = nil
+    @options = @state_context.validate(unless: lambda { @condition_result })[0]
+  end
+
+  def test_should_have_if_option
+    refute_nil @options[:if]
+  end
+
+  def test_should_be_false_if_state_is_different
+    @object.state = nil
+    refute @options[:if].call(@object)
+  end
+
+  def test_should_be_false_if_original_condition_is_true
+    @condition_result = true
+    refute @options[:if].call(@object)
+  end
+
+  def test_should_be_true_if_state_matches_and_original_condition_is_false
+    @condition_result = false
+    assert @options[:if].call(@object)
+  end
+
+  def test_should_evaluate_symbol_condition
+    @klass.class_eval do
+      attr_accessor :callback
+    end
+
+    options = @state_context.validate(unless: :callback)[0]
+
+    object = @klass.new
+    object.callback = true
+    refute options[:if].call(object)
+
+    object.callback = false
+    assert options[:if].call(object)
+  end
+
+  def test_should_evaluate_string_condition
+    @klass.class_eval do
+      attr_accessor :callback
+    end
+
+    options = @state_context.validate(unless: '@callback')[0]
+
+    object = @klass.new
+    object.callback = true
+    refute options[:if].call(object)
+
+    object.callback = false
+    assert options[:if].call(object)
+  end
+end
diff --git a/test/unit/state_context/state_context_proxy_without_conditions_test.rb b/test/unit/state_context/state_context_proxy_without_conditions_test.rb
new file mode 100644
index 0000000..6b3ca04
--- /dev/null
+++ b/test/unit/state_context/state_context_proxy_without_conditions_test.rb
@@ -0,0 +1,31 @@
+require_relative '../../test_helper'
+
+class StateContextProxyWithoutConditionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    machine = StateMachines::Machine.new(@klass, initial: :parked)
+    state = machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(state)
+    @object = @klass.new
+
+    @options = @state_context.validate[0]
+  end
+
+  def test_should_have_options_configuration
+    assert_instance_of Hash, @options
+  end
+
+  def test_should_have_if_option
+    refute_nil @options[:if]
+  end
+
+  def test_should_be_false_if_state_is_different
+    @object.state = nil
+    refute @options[:if].call(@object)
+  end
+
+  def test_should_be_true_if_state_matches
+    assert @options[:if].call(@object)
+  end
+end
diff --git a/test/unit/state_context/state_context_test.rb b/test/unit/state_context/state_context_test.rb
new file mode 100644
index 0000000..8f056c7
--- /dev/null
+++ b/test/unit/state_context/state_context_test.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class Validateable
+  class << self
+    def validate(*args, &block)
+      args << block if block_given?
+      args
+    end
+  end
+end
+
+class StateContextTest < StateMachinesTest
+  def setup
+    @klass = Class.new(Validateable)
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @state = @machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(@state)
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @state_context.machine
+  end
+
+  def test_should_have_a_state
+    assert_equal @state, @state_context.state
+  end
+end
diff --git a/test/unit/state_context/state_context_transition_test.rb b/test/unit/state_context/state_context_transition_test.rb
new file mode 100644
index 0000000..7a0e93f
--- /dev/null
+++ b/test/unit/state_context/state_context_transition_test.rb
@@ -0,0 +1,104 @@
+require_relative '../../test_helper'
+
+class StateContextTransitionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @state = @machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(@state)
+  end
+
+  def test_should_not_allow_except_to
+    exception = assert_raises(ArgumentError) { @state_context.transition(except_to: :idling) }
+    assert_equal 'Unknown key: :except_to. Valid keys are: :from, :to, :on, :if, :unless', exception.message
+  end
+
+  def test_should_not_allow_except_from
+    exception = assert_raises(ArgumentError) { @state_context.transition(except_from: :idling) }
+    assert_equal 'Unknown key: :except_from. Valid keys are: :from, :to, :on, :if, :unless', exception.message
+  end
+
+  def test_should_not_allow_implicit_transitions
+    exception = assert_raises(ArgumentError) { @state_context.transition(parked: :idling) }
+    assert_equal 'Unknown key: :parked. Valid keys are: :from, :to, :on, :if, :unless', exception.message
+  end
+
+  def test_should_not_allow_except_on
+    exception = assert_raises(ArgumentError) { @state_context.transition(except_on: :park) }
+    assert_equal 'Unknown key: :except_on. Valid keys are: :from, :to, :on, :if, :unless', exception.message
+  end
+
+  def test_should_require_on_event
+    exception = assert_raises(ArgumentError) { @state_context.transition(to: :idling) }
+    assert_equal 'Must specify :on event', exception.message
+  end
+
+  def test_should_not_allow_missing_from_and_to
+    exception = assert_raises(ArgumentError) { @state_context.transition(on: :ignite) }
+    assert_equal 'Must specify either :to or :from state', exception.message
+  end
+
+  def test_should_not_allow_from_and_to
+    exception = assert_raises(ArgumentError) { @state_context.transition(on: :ignite, from: :parked, to: :idling) }
+    assert_equal 'Must specify either :to or :from state', exception.message
+  end
+
+  def test_should_allow_to_state_if_missing_from_state
+    @state_context.transition(on: :park, from: :parked)
+  end
+
+  def test_should_allow_from_state_if_missing_to_state
+    @state_context.transition(on: :ignite, to: :idling)
+  end
+
+  def test_should_automatically_set_to_option_with_from_state
+    branch = @state_context.transition(from: :idling, on: :park)
+    assert_instance_of StateMachines::Branch, branch
+
+    state_requirements = branch.state_requirements
+    assert_equal 1, state_requirements.length
+
+    from_requirement = state_requirements[0][:to]
+    assert_instance_of StateMachines::WhitelistMatcher, from_requirement
+    assert_equal [:parked], from_requirement.values
+  end
+
+  def test_should_automatically_set_from_option_with_to_state
+    branch = @state_context.transition(to: :idling, on: :ignite)
+    assert_instance_of StateMachines::Branch, branch
+
+    state_requirements = branch.state_requirements
+    assert_equal 1, state_requirements.length
+
+    from_requirement = state_requirements[0][:from]
+    assert_instance_of StateMachines::WhitelistMatcher, from_requirement
+    assert_equal [:parked], from_requirement.values
+  end
+
+  def test_should_allow_if_condition
+    @state_context.transition(to: :idling, on: :park, if: :seatbelt_on?)
+  end
+
+  def test_should_allow_unless_condition
+    @state_context.transition(to: :idling, on: :park, unless: :seatbelt_off?)
+  end
+
+  def test_should_include_all_transition_states_in_machine_states
+    @state_context.transition(to: :idling, on: :ignite)
+
+    assert_equal [:parked, :idling], @machine.states.map { |state| state.name }
+  end
+
+  def test_should_include_all_transition_events_in_machine_events
+    @state_context.transition(to: :idling, on: :ignite)
+
+    assert_equal [:ignite], @machine.events.map { |event| event.name }
+  end
+
+  def test_should_allow_multiple_events
+    @state_context.transition(to: :idling, on: [:ignite, :shift_up])
+
+    assert_equal [:ignite, :shift_up], @machine.events.map { |event| event.name }
+  end
+end
diff --git a/test/unit/state_context/state_context_with_matching_transition_test.rb b/test/unit/state_context/state_context_with_matching_transition_test.rb
new file mode 100644
index 0000000..d959b46
--- /dev/null
+++ b/test/unit/state_context/state_context_with_matching_transition_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class StateContextWithMatchingTransitionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @state = @machine.state :parked
+
+    @state_context = StateMachines::StateContext.new(@state)
+    @state_context.transition(to: :idling, on: :ignite)
+
+    @event = @machine.event(:ignite)
+    @object = @klass.new
+  end
+
+  def test_should_be_able_to_fire
+    assert @event.can_fire?(@object)
+  end
+
+  def test_should_have_a_transition
+    transition = @event.transition_for(@object)
+    refute_nil transition
+    assert_equal 'parked', transition.from
+    assert_equal 'idling', transition.to
+    assert_equal :ignite, transition.event
+  end
+end
diff --git a/test/unit/state_machine/state_machine_by_default_test.rb b/test/unit/state_machine/state_machine_by_default_test.rb
new file mode 100644
index 0000000..0e2d34d
--- /dev/null
+++ b/test/unit/state_machine/state_machine_by_default_test.rb
@@ -0,0 +1,12 @@
+require_relative '../../test_helper'
+
+class StateMachineByDefaultTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = @klass.state_machine
+  end
+
+  def test_should_use_state_attribute
+    assert_equal :state, @machine.attribute
+  end
+end
diff --git a/test/unit/state_machine/state_machine_test.rb b/test/unit/state_machine/state_machine_test.rb
new file mode 100644
index 0000000..38a4240
--- /dev/null
+++ b/test/unit/state_machine/state_machine_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class StateMachineTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+  end
+
+  def test_should_allow_state_machines_on_any_class
+    assert @klass.respond_to?(:state_machine)
+  end
+
+  def test_should_evaluate_block_within_machine_context
+    responded = false
+    @klass.state_machine(:state) do
+      responded = respond_to?(:event)
+    end
+
+    assert responded
+  end
+end
diff --git a/test/unit/transition/transition_after_being_performed_test.rb b/test/unit/transition/transition_after_being_performed_test.rb
new file mode 100644
index 0000000..5f73614
--- /dev/null
+++ b/test/unit/transition/transition_after_being_performed_test.rb
@@ -0,0 +1,48 @@
+require_relative '../../test_helper'
+
+class TransitionAfterBeingPerformedTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :saved, :save_state
+
+      def save
+        @save_state = state
+        @saved = true
+        1
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    @result = @transition.perform
+  end
+
+  def test_should_have_empty_args
+    assert_equal [], @transition.args
+  end
+
+  def test_should_have_a_result
+    assert_equal 1, @transition.result
+  end
+
+  def test_should_be_successful
+    assert_equal true, @result
+  end
+
+  def test_should_change_the_current_state
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_run_the_action
+    assert @object.saved
+  end
+
+  def test_should_run_the_action_after_saving_the_state
+    assert_equal 'idling', @object.save_state
+  end
+end
diff --git a/test/unit/transition/transition_after_being_persisted_test.rb b/test/unit/transition/transition_after_being_persisted_test.rb
new file mode 100644
index 0000000..91c678f
--- /dev/null
+++ b/test/unit/transition/transition_after_being_persisted_test.rb
@@ -0,0 +1,46 @@
+require_relative '../../test_helper'
+
+class TransitionAfterBeingPersistedTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    @transition.persist
+  end
+
+  def test_should_update_state_value
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_not_change_from_state
+    assert_equal 'parked', @transition.from
+  end
+
+  def test_should_not_change_to_state
+    assert_equal 'idling', @transition.to
+  end
+
+  def test_should_not_be_able_to_persist_twice
+    @object.state = 'parked'
+    @transition.persist
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_be_able_to_persist_again_after_resetting
+    @object.state = 'parked'
+    @transition.reset
+    @transition.persist
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_revert_to_from_state_on_rollback
+    @transition.rollback
+    assert_equal 'parked', @object.state
+  end
+end
diff --git a/test/unit/transition/transition_after_being_rolled_back_test.rb b/test/unit/transition/transition_after_being_rolled_back_test.rb
new file mode 100644
index 0000000..099dc52
--- /dev/null
+++ b/test/unit/transition/transition_after_being_rolled_back_test.rb
@@ -0,0 +1,35 @@
+require_relative '../../test_helper'
+
+class TransitionAfterBeingRolledBackTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    @object.state = 'idling'
+
+    @transition.rollback
+  end
+
+  def test_should_update_state_value_to_from_state
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_change_from_state
+    assert_equal 'parked', @transition.from
+  end
+
+  def test_should_not_change_to_state
+    assert_equal 'idling', @transition.to
+  end
+
+  def test_should_still_be_able_to_persist
+    @transition.persist
+    assert_equal 'idling', @object.state
+  end
+end
diff --git a/test/unit/transition/transition_equality_test.rb b/test/unit/transition/transition_equality_test.rb
new file mode 100644
index 0000000..3751bc7
--- /dev/null
+++ b/test/unit/transition/transition_equality_test.rb
@@ -0,0 +1,52 @@
+require_relative '../../test_helper'
+
+class TransitionEqualityTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_be_equal_with_same_properties
+    transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    assert_equal transition, @transition
+  end
+
+  def test_should_not_be_equal_with_different_machines
+    machine = StateMachines::Machine.new(@klass, :status, namespace: :other)
+    machine.state :parked, :idling
+    machine.event :ignite
+    transition = StateMachines::Transition.new(@object, machine, :ignite, :parked, :idling)
+
+    refute_equal transition, @transition
+  end
+
+  def test_should_not_be_equal_with_different_objects
+    transition = StateMachines::Transition.new(@klass.new, @machine, :ignite, :parked, :idling)
+    refute_equal transition, @transition
+  end
+
+  def test_should_not_be_equal_with_different_event_names
+    @machine.event :park
+    transition = StateMachines::Transition.new(@object, @machine, :park, :parked, :idling)
+    refute_equal transition, @transition
+  end
+
+  def test_should_not_be_equal_with_different_from_state_names
+    @machine.state :first_gear
+    transition = StateMachines::Transition.new(@object, @machine, :ignite, :first_gear, :idling)
+    refute_equal transition, @transition
+  end
+
+  def test_should_not_be_equal_with_different_to_state_names
+    @machine.state :first_gear
+    transition = StateMachines::Transition.new(@object, @machine, :ignite, :idling, :first_gear)
+    refute_equal transition, @transition
+  end
+end
diff --git a/test/unit/transition/transition_loopback_test.rb b/test/unit/transition/transition_loopback_test.rb
new file mode 100644
index 0000000..5ed4301
--- /dev/null
+++ b/test/unit/transition/transition_loopback_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class TransitionLoopbackTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+    @machine.event :park
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :park, :parked, :parked)
+  end
+
+  def test_should_be_loopback
+    assert @transition.loopback?
+  end
+end
diff --git a/test/unit/transition/transition_test.rb b/test/unit/transition/transition_test.rb
new file mode 100644
index 0000000..b62739e
--- /dev/null
+++ b/test/unit/transition/transition_test.rb
@@ -0,0 +1,96 @@
+require_relative '../../test_helper'
+
+class TransitionTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_have_an_object
+    assert_equal @object, @transition.object
+  end
+
+  def test_should_have_a_machine
+    assert_equal @machine, @transition.machine
+  end
+
+  def test_should_have_an_event
+    assert_equal :ignite, @transition.event
+  end
+
+  def test_should_have_a_qualified_event
+    assert_equal :ignite, @transition.qualified_event
+  end
+
+  def test_should_have_a_human_event
+    assert_equal 'ignite', @transition.human_event
+  end
+
+  def test_should_have_a_from_value
+    assert_equal 'parked', @transition.from
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :parked, @transition.from_name
+  end
+
+  def test_should_have_a_qualified_from_name
+    assert_equal :parked, @transition.qualified_from_name
+  end
+
+  def test_should_have_a_human_from_name
+    assert_equal 'parked', @transition.human_from_name
+  end
+
+  def test_should_have_a_to_value
+    assert_equal 'idling', @transition.to
+  end
+
+  def test_should_have_a_to_name
+    assert_equal :idling, @transition.to_name
+  end
+
+  def test_should_have_a_qualified_to_name
+    assert_equal :idling, @transition.qualified_to_name
+  end
+
+  def test_should_have_a_human_to_name
+    assert_equal 'idling', @transition.human_to_name
+  end
+
+  def test_should_have_an_attribute
+    assert_equal :state, @transition.attribute
+  end
+
+  def test_should_not_have_an_action
+    assert_nil @transition.action
+  end
+
+  def test_should_not_be_transient
+    assert_equal false, @transition.transient?
+  end
+
+  def test_should_generate_attributes
+    expected = { object: @object, attribute: :state, event: :ignite, from: 'parked', to: 'idling' }
+    assert_equal expected, @transition.attributes
+  end
+
+  def test_should_have_empty_args
+    assert_equal [], @transition.args
+  end
+
+  def test_should_not_have_a_result
+    assert_nil @transition.result
+  end
+
+  def test_should_use_pretty_inspect
+    assert_equal '#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>', @transition.inspect
+  end
+end
diff --git a/test/unit/transition/transition_transient_test.rb b/test/unit/transition/transition_transient_test.rb
new file mode 100644
index 0000000..e8a5be3
--- /dev/null
+++ b/test/unit/transition/transition_transient_test.rb
@@ -0,0 +1,20 @@
+require_relative '../../test_helper'
+
+class TransitionTransientTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    @transition.transient = true
+  end
+
+  def test_should_be_transient
+    assert @transition.transient?
+  end
+end
diff --git a/test/unit/transition/transition_with_action_test.rb b/test/unit/transition/transition_with_action_test.rb
new file mode 100644
index 0000000..e42a2f1
--- /dev/null
+++ b/test/unit/transition/transition_with_action_test.rb
@@ -0,0 +1,27 @@
+require_relative '../../test_helper'
+
+class TransitionWithActionTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_have_an_action
+    assert_equal :save, @transition.action
+  end
+
+  def test_should_not_have_a_result
+    assert_nil @transition.result
+  end
+end
diff --git a/test/unit/transition/transition_with_after_callbacks_skipped_test.rb b/test/unit/transition/transition_with_after_callbacks_skipped_test.rb
new file mode 100644
index 0000000..f071c78
--- /dev/null
+++ b/test/unit/transition/transition_with_after_callbacks_skipped_test.rb
@@ -0,0 +1,127 @@
+require_relative '../../test_helper'
+
+class TransitionWithAfterCallbacksSkippedTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_run_before_callbacks
+    @machine.before_transition { @run = true }
+
+    assert_equal true, @transition.run_callbacks(after: false)
+    assert @run
+  end
+
+  def test_should_not_run_after_callbacks
+    @run = false
+    @machine.after_transition { @run = true }
+
+    assert_equal true, @transition.run_callbacks(after: false)
+    refute @run
+  end
+
+  if StateMachines::Transition.pause_supported?
+    def test_should_run_around_callbacks_before_yield
+      @machine.around_transition { |block| @run = true; block.call }
+
+      assert_equal true, @transition.run_callbacks(after: false)
+      assert @run
+    end
+
+    def test_should_not_run_around_callbacks_after_yield
+      @run = false
+      @machine.around_transition { |block| block.call; @run = true }
+
+      assert_equal true, @transition.run_callbacks(after: false)
+      refute @run
+    end
+
+    def test_should_continue_around_transition_execution_on_second_call
+      @callbacks = []
+      @machine.around_transition { |block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1 }
+      @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2 }
+      @machine.after_transition { @callbacks << :after }
+
+      assert_equal true, @transition.run_callbacks(after: false)
+      assert_equal [:before_around_1, :before_around_2], @callbacks
+
+      assert_equal true, @transition.run_callbacks
+      assert_equal [:before_around_1, :before_around_2, :after_around_2, :after_around_1, :after], @callbacks
+    end
+
+    def test_should_not_run_further_callbacks_if_halted_during_continue_around_transition
+      @callbacks = []
+      @machine.around_transition { |block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1 }
+      @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2; throw :halt }
+      @machine.after_transition { @callbacks << :after }
+
+      assert_equal true, @transition.run_callbacks(after: false)
+      assert_equal [:before_around_1, :before_around_2], @callbacks
+
+      assert_equal true, @transition.run_callbacks
+      assert_equal [:before_around_1, :before_around_2, :after_around_2], @callbacks
+    end
+
+    def test_should_not_be_able_to_continue_twice
+      @count = 0
+      @machine.around_transition { |block| block.call; @count += 1 }
+      @machine.after_transition { @count += 1 }
+
+      @transition.run_callbacks(after: false)
+
+      2.times do
+        assert_equal true, @transition.run_callbacks
+        assert_equal 2, @count
+      end
+    end
+
+    def test_should_not_be_able_to_continue_again_after_halted
+      @count = 0
+      @machine.around_transition { |block| block.call; @count += 1; throw :halt }
+      @machine.after_transition { @count += 1 }
+
+      @transition.run_callbacks(after: false)
+
+      2.times do
+        assert_equal true, @transition.run_callbacks
+        assert_equal 1, @count
+      end
+    end
+
+    def test_should_have_access_to_result_after_continued
+      @machine.around_transition { |block| @around_before_result = @transition.result; block.call; @around_after_result = @transition.result }
+      @machine.after_transition { @after_result = @transition.result }
+
+      @transition.run_callbacks(after: false)
+      @transition.run_callbacks { { result: 1 } }
+
+      assert_nil @around_before_result
+      assert_equal 1, @around_after_result
+      assert_equal 1, @after_result
+    end
+
+    def test_should_raise_exceptions_during_around_callbacks_after_yield_in_second_execution
+      @machine.around_transition { |block| block.call; fail ArgumentError }
+
+      @transition.run_callbacks(after: false)
+      assert_raises(ArgumentError) { @transition.run_callbacks }
+    end
+  else
+    def test_should_raise_exception_on_second_call
+      @callbacks = []
+      @machine.around_transition { |block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1 }
+      @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2 }
+      @machine.after_transition { @callbacks << :after }
+
+      assert_raises(ArgumentError) { @transition.run_callbacks(after: false) }
+    end
+  end
+end
diff --git a/test/unit/transition/transition_with_after_callbacks_test.rb b/test/unit/transition/transition_with_after_callbacks_test.rb
new file mode 100644
index 0000000..07c2d18
--- /dev/null
+++ b/test/unit/transition/transition_with_after_callbacks_test.rb
@@ -0,0 +1,93 @@
+require_relative '../../test_helper'
+
+class TransitionWithAfterCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_run_after_callbacks
+    @machine.after_transition { |_object| @run = true }
+    result = @transition.run_callbacks
+
+    assert_equal true, result
+    assert_equal true, @run
+  end
+
+  def test_should_only_run_those_that_match_transition_context
+    @count = 0
+    callback = lambda { @count += 1 }
+
+    @machine.after_transition from: :parked, to: :idling, on: :park, do: callback
+    @machine.after_transition from: :parked, to: :parked, on: :park, do: callback
+    @machine.after_transition from: :parked, to: :idling, on: :ignite, do: callback
+    @machine.after_transition from: :idling, to: :idling, on: :park, do: callback
+    @transition.run_callbacks
+
+    assert_equal 1, @count
+  end
+
+  def test_should_not_run_if_not_successful
+    @run = false
+    @machine.after_transition { |_object| @run = true }
+    @transition.run_callbacks { { success: false } }
+    refute @run
+  end
+
+  def test_should_run_if_successful
+    @machine.after_transition { |_object| @run = true }
+    @transition.run_callbacks { { success: true } }
+    assert @run
+  end
+
+  def test_should_pass_transition_as_argument
+    @machine.after_transition { |*args| @args = args }
+
+    @transition.run_callbacks
+    assert_equal [@object, @transition], @args
+  end
+
+  def test_should_catch_halts
+    @machine.after_transition { throw :halt }
+
+    result = @transition.run_callbacks
+    assert_equal true, result
+  end
+
+  def test_should_not_catch_exceptions
+    @machine.after_transition  { fail ArgumentError }
+    assert_raises(ArgumentError) { @transition.run_callbacks }
+  end
+
+  def test_should_not_be_able_to_run_twice
+    @count = 0
+    @machine.after_transition { @count += 1 }
+    @transition.run_callbacks
+    @transition.run_callbacks
+    assert_equal 1, @count
+  end
+
+  def test_should_not_be_able_to_run_twice_if_halted
+    @count = 0
+    @machine.after_transition { @count += 1; throw :halt }
+    @transition.run_callbacks
+    @transition.run_callbacks
+    assert_equal 1, @count
+  end
+
+  def test_should_be_able_to_run_again_after_resetting
+    @count = 0
+    @machine.after_transition { @count += 1 }
+    @transition.run_callbacks
+    @transition.reset
+    @transition.run_callbacks
+    assert_equal 2, @count
+  end
+end
diff --git a/test/unit/transition/transition_with_around_callbacks_test.rb b/test/unit/transition/transition_with_around_callbacks_test.rb
new file mode 100644
index 0000000..872d9d8
--- /dev/null
+++ b/test/unit/transition/transition_with_around_callbacks_test.rb
@@ -0,0 +1,141 @@
+require_relative '../../test_helper'
+
+class TransitionWithAroundCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_run_around_callbacks
+    @machine.around_transition { |_object, _transition, block| @run_before = true; block.call; @run_after = true }
+    result = @transition.run_callbacks
+
+    assert_equal true, result
+    assert_equal true, @run_before
+    assert_equal true, @run_after
+  end
+
+  def test_should_only_run_those_that_match_transition_context
+    @count = 0
+    callback = lambda { |_object, _transition, block| @count += 1; block.call }
+
+    @machine.around_transition from: :parked, to: :idling, on: :park, do: callback
+    @machine.around_transition from: :parked, to: :parked, on: :park, do: callback
+    @machine.around_transition from: :parked, to: :idling, on: :ignite, do: callback
+    @machine.around_transition from: :idling, to: :idling, on: :park, do: callback
+    @transition.run_callbacks
+
+    assert_equal 1, @count
+  end
+
+  def test_should_pass_transition_as_argument
+    @machine.around_transition { |*args| block = args.pop; @args = args; block.call }
+    @transition.run_callbacks
+
+    assert_equal [@object, @transition], @args
+  end
+
+  def test_should_run_block_between_callback
+    @callbacks = []
+    @machine.around_transition { |block| @callbacks << :before; block.call; @callbacks << :after }
+    @transition.run_callbacks { @callbacks << :within; { success: true } }
+
+    assert_equal [:before, :within, :after], @callbacks
+  end
+
+  def test_should_have_access_to_result_after_yield
+    @machine.around_transition { |block| @before_result = @transition.result; block.call; @after_result = @transition.result }
+    @transition.run_callbacks { { result: 1, success: true } }
+
+    assert_nil @before_result
+    assert_equal 1, @after_result
+  end
+
+  def test_should_catch_before_yield_halts
+    @machine.around_transition { throw :halt }
+
+    result = @transition.run_callbacks
+    assert_equal false, result
+  end
+
+  def test_should_catch_after_yield_halts
+    @machine.around_transition { |block| block.call; throw :halt }
+
+    result = @transition.run_callbacks
+    assert_equal true, result
+  end
+
+  def test_should_not_catch_before_yield
+    @machine.around_transition  { fail ArgumentError }
+    assert_raises(ArgumentError) { @transition.run_callbacks }
+  end
+
+  def test_should_not_catch_after_yield
+    @machine.around_transition { |block| block.call; fail ArgumentError }
+    assert_raises(ArgumentError) { @transition.run_callbacks }
+  end
+
+  def test_should_fail_if_not_yielded
+    @machine.around_transition {}
+
+    result = @transition.run_callbacks
+    assert_equal false, result
+  end
+
+  def test_should_not_be_able_to_run_twice
+    @before_count = 0
+    @after_count = 0
+    @machine.around_transition { |block| @before_count += 1; block.call; @after_count += 1 }
+    @transition.run_callbacks
+    @transition.run_callbacks
+    assert_equal 1, @before_count
+    assert_equal 1, @after_count
+  end
+
+  def test_should_be_able_to_run_again_after_resetting
+    @before_count = 0
+    @after_count = 0
+    @machine.around_transition { |block| @before_count += 1; block.call; @after_count += 1 }
+    @transition.run_callbacks
+    @transition.reset
+    @transition.run_callbacks
+    assert_equal 2, @before_count
+    assert_equal 2, @after_count
+  end
+
+  def test_should_succeed_if_block_result_is_false
+    @machine.around_transition { |block| @before_run = true; block.call; @after_run = true }
+    assert_equal true, @transition.run_callbacks { { success: true, result: false } }
+    assert @before_run
+    assert @after_run
+  end
+
+  def test_should_succeed_if_block_result_is_true
+    @machine.around_transition { |block| @before_run = true; block.call; @after_run = true }
+    assert_equal true, @transition.run_callbacks { { success: true, result: true } }
+    assert @before_run
+    assert @after_run
+  end
+
+  def test_should_only_run_before_if_block_success_is_false
+    @after_run = false
+    @machine.around_transition { |block| @before_run = true; block.call; @after_run = true }
+    assert_equal true, @transition.run_callbacks { { success: false } }
+    assert @before_run
+    refute @after_run
+  end
+
+  def test_should_succeed_if_block_success_is_false
+    @machine.around_transition { |block| @before_run = true; block.call; @after_run = true }
+    assert_equal true, @transition.run_callbacks { { success: true } }
+    assert @before_run
+    assert @after_run
+  end
+end
diff --git a/test/unit/transition/transition_with_before_callbacks_skipped_test.rb b/test/unit/transition/transition_with_before_callbacks_skipped_test.rb
new file mode 100644
index 0000000..f66f36c
--- /dev/null
+++ b/test/unit/transition/transition_with_before_callbacks_skipped_test.rb
@@ -0,0 +1,30 @@
+require_relative '../../test_helper'
+
+class TransitionWithBeforeCallbacksSkippedTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_not_run_before_callbacks
+    @run = false
+    @machine.before_transition { @run = true }
+
+    assert_equal false, @transition.run_callbacks(before: false)
+    refute @run
+  end
+
+  def test_should_run_failure_callbacks
+    @machine.after_failure { @run = true }
+
+    assert_equal false, @transition.run_callbacks(before: false)
+    assert @run
+  end
+end
diff --git a/test/unit/transition/transition_with_before_callbacks_test.rb b/test/unit/transition/transition_with_before_callbacks_test.rb
new file mode 100644
index 0000000..d749ac5
--- /dev/null
+++ b/test/unit/transition/transition_with_before_callbacks_test.rb
@@ -0,0 +1,104 @@
+require_relative '../../test_helper'
+
+class TransitionWithBeforeCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_run_before_callbacks
+    @machine.before_transition { @run = true }
+    result = @transition.run_callbacks
+
+    assert_equal true, result
+    assert_equal true, @run
+  end
+
+  def test_should_only_run_those_that_match_transition_context
+    @count = 0
+    callback = lambda { @count += 1 }
+
+    @machine.before_transition from: :parked, to: :idling, on: :park, do: callback
+    @machine.before_transition from: :parked, to: :parked, on: :park, do: callback
+    @machine.before_transition from: :parked, to: :idling, on: :ignite, do: callback
+    @machine.before_transition from: :idling, to: :idling, on: :park, do: callback
+    @transition.run_callbacks
+
+    assert_equal 1, @count
+  end
+
+  def test_should_pass_transition_as_argument
+    @machine.before_transition { |*args| @args = args }
+    @transition.run_callbacks
+
+    assert_equal [@object, @transition], @args
+  end
+
+  def test_should_catch_halts
+    @machine.before_transition { throw :halt }
+
+    result = @transition.run_callbacks
+    assert_equal false, result
+  end
+
+  def test_should_not_catch_exceptions
+    @machine.before_transition { fail ArgumentError }
+    assert_raises(ArgumentError) { @transition.run_callbacks }
+  end
+
+  def test_should_not_be_able_to_run_twice
+    @count = 0
+    @machine.before_transition { @count += 1 }
+    @transition.run_callbacks
+    @transition.run_callbacks
+    assert_equal 1, @count
+  end
+
+  def test_should_be_able_to_run_again_after_halt
+    @count = 0
+    @machine.before_transition { @count += 1; throw :halt }
+    @transition.run_callbacks
+    @transition.run_callbacks
+    assert_equal 2, @count
+  end
+
+  def test_should_be_able_to_run_again_after_resetting
+    @count = 0
+    @machine.before_transition { @count += 1 }
+    @transition.run_callbacks
+    @transition.reset
+    @transition.run_callbacks
+    assert_equal 2, @count
+  end
+
+  def test_should_succeed_if_block_result_is_false
+    @machine.before_transition { @run = true }
+    assert_equal true, @transition.run_callbacks { { result: false } }
+    assert @run
+  end
+
+  def test_should_succeed_if_block_result_is_true
+    @machine.before_transition { @run = true }
+    assert_equal true, @transition.run_callbacks { { result: true } }
+    assert @run
+  end
+
+  def test_should_succeed_if_block_success_is_false
+    @machine.before_transition { @run = true }
+    assert_equal true, @transition.run_callbacks { { success: false } }
+    assert @run
+  end
+
+  def test_should_succeed_if_block_success_is_true
+    @machine.before_transition { @run = true }
+    assert_equal true, @transition.run_callbacks { { success: true } }
+    assert @run
+  end
+end
diff --git a/test/unit/transition/transition_with_custom_machine_attribute_test.rb b/test/unit/transition/transition_with_custom_machine_attribute_test.rb
new file mode 100644
index 0000000..507ee50
--- /dev/null
+++ b/test/unit/transition/transition_with_custom_machine_attribute_test.rb
@@ -0,0 +1,28 @@
+require_relative '../../test_helper'
+
+class TransitionWithCustomMachineAttributeTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, :state, attribute: :state_id)
+    @machine.state :off, value: 1
+    @machine.state :active, value: 2
+    @machine.event :activate
+
+    @object = @klass.new
+    @object.state_id = 1
+
+    @transition = StateMachines::Transition.new(@object, @machine, :activate, :off, :active)
+  end
+
+  def test_should_persist
+    @transition.persist
+    assert_equal 2, @object.state_id
+  end
+
+  def test_should_rollback
+    @object.state_id = 2
+    @transition.rollback
+
+    assert_equal 1, @object.state_id
+  end
+end
diff --git a/test/unit/transition/transition_with_different_states_test.rb b/test/unit/transition/transition_with_different_states_test.rb
new file mode 100644
index 0000000..a0ee2d2
--- /dev/null
+++ b/test/unit/transition/transition_with_different_states_test.rb
@@ -0,0 +1,18 @@
+require_relative '../../test_helper'
+
+class TransitionWithDifferentStatesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_not_be_loopback
+    refute @transition.loopback?
+  end
+end
diff --git a/test/unit/transition/transition_with_dynamic_to_value_test.rb b/test/unit/transition/transition_with_dynamic_to_value_test.rb
new file mode 100644
index 0000000..b1c4abc
--- /dev/null
+++ b/test/unit/transition/transition_with_dynamic_to_value_test.rb
@@ -0,0 +1,19 @@
+require_relative '../../test_helper'
+
+class TransitionWithDynamicToValueTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked
+    @machine.state :idling, value: lambda { 1 }
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_evaluate_to_value
+    assert_equal 1, @transition.to
+  end
+end
diff --git a/test/unit/transition/transition_with_failure_callbacks_test.rb b/test/unit/transition/transition_with_failure_callbacks_test.rb
new file mode 100644
index 0000000..9a19a84
--- /dev/null
+++ b/test/unit/transition/transition_with_failure_callbacks_test.rb
@@ -0,0 +1,84 @@
+require_relative '../../test_helper'
+
+class TransitionWithFailureCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_only_run_those_that_match_transition_context
+    @count = 0
+    callback = lambda { @count += 1 }
+
+    @machine.after_failure do: callback
+    @machine.after_failure on: :park, do: callback
+    @machine.after_failure on: :ignite, do: callback
+    @transition.run_callbacks { { success: false } }
+
+    assert_equal 2, @count
+  end
+
+  def test_should_run_if_not_successful
+    @machine.after_failure { |_object| @run = true }
+    @transition.run_callbacks { { success: false } }
+    assert @run
+  end
+
+  def test_should_not_run_if_successful
+    @run = false
+    @machine.after_failure { |_object| @run = true }
+    @transition.run_callbacks { { success: true } }
+    refute @run
+  end
+
+  def test_should_pass_transition_as_argument
+    @machine.after_failure { |*args| @args = args }
+
+    @transition.run_callbacks { { success: false } }
+    assert_equal [@object, @transition], @args
+  end
+
+  def test_should_catch_halts
+    @machine.after_failure { throw :halt }
+
+    result = @transition.run_callbacks { { success: false } }
+    assert_equal true, result
+  end
+
+  def test_should_not_catch_exceptions
+    @machine.after_failure  { fail ArgumentError }
+    assert_raises(ArgumentError) { @transition.run_callbacks { { success: false } } }
+  end
+
+  def test_should_not_be_able_to_run_twice
+    @count = 0
+    @machine.after_failure { @count += 1 }
+    @transition.run_callbacks { { success: false } }
+    @transition.run_callbacks { { success: false } }
+    assert_equal 1, @count
+  end
+
+  def test_should_not_be_able_to_run_twice_if_halted
+    @count = 0
+    @machine.after_failure { @count += 1; throw :halt }
+    @transition.run_callbacks { { success: false } }
+    @transition.run_callbacks { { success: false } }
+    assert_equal 1, @count
+  end
+
+  def test_should_be_able_to_run_again_after_resetting
+    @count = 0
+    @machine.after_failure { @count += 1 }
+    @transition.run_callbacks { { success: false } }
+    @transition.reset
+    @transition.run_callbacks { { success: false } }
+    assert_equal 2, @count
+  end
+end
diff --git a/test/unit/transition/transition_with_invalid_nodes_test.rb b/test/unit/transition/transition_with_invalid_nodes_test.rb
new file mode 100644
index 0000000..f1dd9f8
--- /dev/null
+++ b/test/unit/transition/transition_with_invalid_nodes_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class TransitionWithInvalidNodesTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+  end
+
+  def test_should_raise_exception_without_event
+    assert_raises(IndexError) { StateMachines::Transition.new(@object, @machine, nil, :parked, :idling) }
+  end
+
+  def test_should_raise_exception_with_invalid_event
+    assert_raises(IndexError) { StateMachines::Transition.new(@object, @machine, :invalid, :parked, :idling) }
+  end
+
+  def test_should_raise_exception_with_invalid_from_state
+    assert_raises(IndexError) { StateMachines::Transition.new(@object, @machine, :ignite, :invalid, :idling) }
+  end
+
+  def test_should_raise_exception_with_invalid_to_state
+    assert_raises(IndexError) { StateMachines::Transition.new(@object, @machine, :ignite, :parked, :invalid) }
+  end
+end
diff --git a/test/unit/transition/transition_with_mixed_callbacks_test.rb b/test/unit/transition/transition_with_mixed_callbacks_test.rb
new file mode 100644
index 0000000..588d9c4
--- /dev/null
+++ b/test/unit/transition/transition_with_mixed_callbacks_test.rb
@@ -0,0 +1,105 @@
+require_relative '../../test_helper'
+
+class TransitionWithMixedCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_before_and_around_callbacks_in_order_defined
+    @callbacks = []
+    @machine.before_transition { @callbacks << :before_1 }
+    @machine.around_transition { |block| @callbacks << :around; block.call }
+    @machine.before_transition { @callbacks << :before_2 }
+
+    assert_equal true, @transition.run_callbacks
+    assert_equal [:before_1, :around, :before_2], @callbacks
+  end
+
+  def test_should_run_around_callbacks_before_after_callbacks
+    @callbacks = []
+    @machine.after_transition { @callbacks << :after_1 }
+    @machine.around_transition { |block| block.call; @callbacks << :after_2 }
+    @machine.after_transition { @callbacks << :after_3 }
+
+    assert_equal true, @transition.run_callbacks
+    assert_equal [:after_2, :after_1, :after_3], @callbacks
+  end
+
+  def test_should_have_access_to_result_for_both_after_and_around_callbacks
+    @machine.after_transition { @after_result = @transition.result }
+    @machine.around_transition { |block| block.call; @around_result = @transition.result }
+
+    @transition.run_callbacks { { result: 1, success: true } }
+    assert_equal 1, @after_result
+    assert_equal 1, @around_result
+  end
+
+  def test_should_not_run_further_callbacks_if_before_callback_halts
+    @callbacks = []
+    @machine.before_transition { @callbacks << :before_1 }
+    @machine.around_transition { |block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1 }
+    @machine.before_transition { @callbacks << :before_2; throw :halt }
+    @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2 }
+    @machine.after_transition { @callbacks << :after }
+
+    assert_equal false, @transition.run_callbacks
+    assert_equal [:before_1, :before_around_1, :before_2], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_before_yield_halts
+    @callbacks = []
+    @machine.before_transition { @callbacks << :before_1 }
+    @machine.around_transition { |_block| @callbacks << :before_around_1; throw :halt }
+    @machine.before_transition { @callbacks << :before_2; throw :halt }
+    @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2 }
+    @machine.after_transition { @callbacks << :after }
+
+    assert_equal false, @transition.run_callbacks
+    assert_equal [:before_1, :before_around_1], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_around_callback_fails_to_yield
+    @callbacks = []
+    @machine.before_transition { @callbacks << :before_1 }
+    @machine.around_transition { |_block| @callbacks << :before_around_1 }
+    @machine.before_transition { @callbacks << :before_2; throw :halt }
+    @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2 }
+    @machine.after_transition { @callbacks << :after }
+
+    assert_equal false, @transition.run_callbacks
+    assert_equal [:before_1, :before_around_1], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_after_yield_halts
+    @callbacks = []
+    @machine.before_transition { @callbacks << :before_1 }
+    @machine.around_transition { |block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1; throw :halt }
+    @machine.before_transition { @callbacks << :before_2 }
+    @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2 }
+    @machine.after_transition { @callbacks << :after }
+
+    assert_equal true, @transition.run_callbacks
+    assert_equal [:before_1, :before_around_1, :before_2, :before_around_2, :after_around_2, :after_around_1], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_after_callback_halts
+    @callbacks = []
+    @machine.before_transition { @callbacks << :before_1 }
+    @machine.around_transition { |block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1 }
+    @machine.before_transition { @callbacks << :before_2 }
+    @machine.around_transition { |block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2 }
+    @machine.after_transition { @callbacks << :after_1; throw :halt }
+    @machine.after_transition { @callbacks << :after_2 }
+
+    assert_equal true, @transition.run_callbacks
+    assert_equal [:before_1, :before_around_1, :before_2, :before_around_2, :after_around_2, :after_around_1, :after_1], @callbacks
+  end
+end
diff --git a/test/unit/transition/transition_with_multiple_after_callbacks_test.rb b/test/unit/transition/transition_with_multiple_after_callbacks_test.rb
new file mode 100644
index 0000000..2ad5da0
--- /dev/null
+++ b/test/unit/transition/transition_with_multiple_after_callbacks_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class TransitionWithMultipleAfterCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_run_in_the_order_they_were_defined
+    @callbacks = []
+    @machine.after_transition { @callbacks << 1 }
+    @machine.after_transition { @callbacks << 2 }
+    @transition.run_callbacks
+
+    assert_equal [1, 2], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_halted
+    @callbacks = []
+    @machine.after_transition { @callbacks << 1; throw :halt }
+    @machine.after_transition { @callbacks << 2 }
+
+    assert_equal true, @transition.run_callbacks
+    assert_equal [1], @callbacks
+  end
+
+  def test_should_fail_if_any_callback_halted
+    @machine.after_transition { true }
+    @machine.after_transition { throw :halt }
+
+    assert_equal true, @transition.run_callbacks
+  end
+end
diff --git a/test/unit/transition/transition_with_multiple_around_callbacks_test.rb b/test/unit/transition/transition_with_multiple_around_callbacks_test.rb
new file mode 100644
index 0000000..13df26d
--- /dev/null
+++ b/test/unit/transition/transition_with_multiple_around_callbacks_test.rb
@@ -0,0 +1,114 @@
+require_relative '../../test_helper'
+
+class TransitionWithMultipleAroundCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_before_yield_in_the_order_they_were_defined
+    @callbacks = []
+    @machine.around_transition { |block| @callbacks << 1; block.call }
+    @machine.around_transition { |block| @callbacks << 2; block.call }
+    @transition.run_callbacks
+
+    assert_equal [1, 2], @callbacks
+  end
+
+  def test_should_before_yield_multiple_methods_in_the_order_they_were_defined
+    @callbacks = []
+    @machine.around_transition(lambda { |block| @callbacks << 1; block.call }, lambda { |block| @callbacks << 2; block.call })
+    @machine.around_transition(lambda { |block| @callbacks << 3; block.call }, lambda { |block| @callbacks << 4; block.call })
+    @transition.run_callbacks
+
+    assert_equal [1, 2, 3, 4], @callbacks
+  end
+
+  def test_should_after_yield_in_the_reverse_order_they_were_defined
+    @callbacks = []
+    @machine.around_transition { |block| block.call; @callbacks << 1 }
+    @machine.around_transition { |block| block.call; @callbacks << 2 }
+    @transition.run_callbacks
+
+    assert_equal [2, 1], @callbacks
+  end
+
+  def test_should_after_yield_multiple_methods_in_the_reverse_order_they_were_defined
+    @callbacks = []
+    @machine.around_transition(lambda { |block| block.call; @callbacks << 1 }) { |block| block.call; @callbacks << 2 }
+    @machine.around_transition(lambda { |block| block.call; @callbacks << 3 }) { |block| block.call; @callbacks << 4 }
+    @transition.run_callbacks
+
+    assert_equal [4, 3, 2, 1], @callbacks
+  end
+
+  def test_should_run_block_between_callback
+    @callbacks = []
+    @machine.around_transition { |block| @callbacks << :before_1; block.call; @callbacks << :after_1 }
+    @machine.around_transition { |block| @callbacks << :before_2; block.call; @callbacks << :after_2 }
+    @transition.run_callbacks { @callbacks << :within; { success: true } }
+
+    assert_equal [:before_1, :before_2, :within, :after_2, :after_1], @callbacks
+  end
+
+  def test_should_have_access_to_result_after_yield
+    @machine.around_transition { |block| @before_result_1 = @transition.result; block.call; @after_result_1 = @transition.result }
+    @machine.around_transition { |block| @before_result_2 = @transition.result; block.call; @after_result_2 = @transition.result }
+    @transition.run_callbacks { { result: 1, success: true } }
+
+    assert_nil @before_result_1
+    assert_nil @before_result_2
+    assert_equal 1, @after_result_1
+    assert_equal 1, @after_result_2
+  end
+
+  def test_should_fail_if_any_before_yield_halted
+    @machine.around_transition { |block| block.call }
+    @machine.around_transition { throw :halt }
+
+    assert_equal false, @transition.run_callbacks
+  end
+
+  def test_should_not_continue_around_callbacks_if_before_yield_halted
+    @callbacks = []
+    @machine.around_transition { @callbacks << 1; throw :halt }
+    @machine.around_transition { |block| @callbacks << 2; block.call; @callbacks << 3 }
+
+    assert_equal false, @transition.run_callbacks
+    assert_equal [1], @callbacks
+  end
+
+  def test_should_not_continue_around_callbacks_if_later_before_yield_halted
+    @callbacks = []
+    @machine.around_transition { |block| block.call; @callbacks << 1 }
+    @machine.around_transition { throw :halt }
+
+    @transition.run_callbacks
+    assert_equal [], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_after_yield_halted
+    @callbacks = []
+    @machine.around_transition { |block| block.call; @callbacks << 1 }
+    @machine.around_transition { |block| block.call; throw :halt }
+
+    assert_equal true, @transition.run_callbacks
+    assert_equal [], @callbacks
+  end
+
+  def test_should_fail_if_any_fail_to_yield
+    @callbacks = []
+    @machine.around_transition { @callbacks << 1 }
+    @machine.around_transition { |block| @callbacks << 2; block.call; @callbacks << 3 }
+
+    assert_equal false, @transition.run_callbacks
+    assert_equal [1], @callbacks
+  end
+end
diff --git a/test/unit/transition/transition_with_multiple_before_callbacks_test.rb b/test/unit/transition/transition_with_multiple_before_callbacks_test.rb
new file mode 100644
index 0000000..8b8d098
--- /dev/null
+++ b/test/unit/transition/transition_with_multiple_before_callbacks_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class TransitionWithMultipleBeforeCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_run_in_the_order_they_were_defined
+    @callbacks = []
+    @machine.before_transition { @callbacks << 1 }
+    @machine.before_transition { @callbacks << 2 }
+    @transition.run_callbacks
+
+    assert_equal [1, 2], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_halted
+    @callbacks = []
+    @machine.before_transition { @callbacks << 1; throw :halt }
+    @machine.before_transition { @callbacks << 2 }
+
+    assert_equal false, @transition.run_callbacks
+    assert_equal [1], @callbacks
+  end
+
+  def test_should_fail_if_any_callback_halted
+    @machine.before_transition { true }
+    @machine.before_transition { throw :halt }
+
+    assert_equal false, @transition.run_callbacks
+  end
+end
diff --git a/test/unit/transition/transition_with_multiple_failure_callbacks_test.rb b/test/unit/transition/transition_with_multiple_failure_callbacks_test.rb
new file mode 100644
index 0000000..272f878
--- /dev/null
+++ b/test/unit/transition/transition_with_multiple_failure_callbacks_test.rb
@@ -0,0 +1,40 @@
+require_relative '../../test_helper'
+
+class TransitionWithMultipleFailureCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_run_in_the_order_they_were_defined
+    @callbacks = []
+    @machine.after_failure { @callbacks << 1 }
+    @machine.after_failure { @callbacks << 2 }
+    @transition.run_callbacks { { success: false } }
+
+    assert_equal [1, 2], @callbacks
+  end
+
+  def test_should_not_run_further_callbacks_if_halted
+    @callbacks = []
+    @machine.after_failure { @callbacks << 1; throw :halt }
+    @machine.after_failure { @callbacks << 2 }
+
+    assert_equal true, @transition.run_callbacks { { success: false } }
+    assert_equal [1], @callbacks
+  end
+
+  def test_should_fail_if_any_callback_halted
+    @machine.after_failure { true }
+    @machine.after_failure { throw :halt }
+
+    assert_equal true, @transition.run_callbacks { { success: false } }
+  end
+end
diff --git a/test/unit/transition/transition_with_namespace_test.rb b/test/unit/transition/transition_with_namespace_test.rb
new file mode 100644
index 0000000..f4e6b33
--- /dev/null
+++ b/test/unit/transition/transition_with_namespace_test.rb
@@ -0,0 +1,47 @@
+require_relative '../../test_helper'
+
+class TransitionWithNamespaceTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass, namespace: 'alarm')
+    @machine.state :off, :active
+    @machine.event :activate
+
+    @object = @klass.new
+    @object.state = 'off'
+
+    @transition = StateMachines::Transition.new(@object, @machine, :activate, :off, :active)
+  end
+
+  def test_should_have_an_event
+    assert_equal :activate, @transition.event
+  end
+
+  def test_should_have_a_qualified_event
+    assert_equal :activate_alarm, @transition.qualified_event
+  end
+
+  def test_should_have_a_from_name
+    assert_equal :off, @transition.from_name
+  end
+
+  def test_should_have_a_qualified_from_name
+    assert_equal :alarm_off, @transition.qualified_from_name
+  end
+
+  def test_should_have_a_human_from_name
+    assert_equal 'off', @transition.human_from_name
+  end
+
+  def test_should_have_a_to_name
+    assert_equal :active, @transition.to_name
+  end
+
+  def test_should_have_a_qualified_to_name
+    assert_equal :alarm_active, @transition.qualified_to_name
+  end
+
+  def test_should_have_a_human_to_name
+    assert_equal 'active', @transition.human_to_name
+  end
+end
diff --git a/test/unit/transition/transition_with_perform_arguments_test.rb b/test/unit/transition/transition_with_perform_arguments_test.rb
new file mode 100644
index 0000000..fee4ffa
--- /dev/null
+++ b/test/unit/transition/transition_with_perform_arguments_test.rb
@@ -0,0 +1,35 @@
+require_relative '../../test_helper'
+
+class TransitionWithPerformArgumentsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :saved
+
+      def save
+        @saved = true
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_have_arguments
+    @transition.perform(1, 2)
+
+    assert_equal [1, 2], @transition.args
+    assert @object.saved
+  end
+
+  def test_should_not_include_run_action_in_arguments
+    @transition.perform(1, 2, false)
+
+    assert_equal [1, 2], @transition.args
+    refute @object.saved
+  end
+end
diff --git a/test/unit/transition/transition_with_transactions_test.rb b/test/unit/transition/transition_with_transactions_test.rb
new file mode 100644
index 0000000..95df3f8
--- /dev/null
+++ b/test/unit/transition/transition_with_transactions_test.rb
@@ -0,0 +1,42 @@
+require_relative '../../test_helper'
+
+class TransitionWithTransactionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      class << self
+        attr_accessor :running_transaction
+      end
+
+      attr_accessor :result
+
+      def save
+        @result = self.class.running_transaction
+        true
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+
+    class << @machine
+      def within_transaction(object)
+        owner_class.running_transaction = object
+        yield
+        owner_class.running_transaction = false
+      end
+    end
+  end
+
+  def test_should_run_blocks_within_transaction_for_object
+    @transition.within_transaction do
+      @result = @klass.running_transaction
+    end
+
+    assert_equal @object, @result
+  end
+end
diff --git a/test/unit/transition/transition_without_callbacks_test.rb b/test/unit/transition/transition_without_callbacks_test.rb
new file mode 100644
index 0000000..7d7306f
--- /dev/null
+++ b/test/unit/transition/transition_without_callbacks_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class TransitionWithoutCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def test_should_succeed
+    assert_equal true, @transition.run_callbacks
+  end
+
+  def test_should_succeed_if_after_callbacks_skipped
+    assert_equal true, @transition.run_callbacks(after: false)
+  end
+
+  def test_should_call_block_if_provided
+    @transition.run_callbacks { @ran_block = true; {} }
+    assert @ran_block
+  end
+
+  def test_should_track_block_result
+    @transition.run_callbacks { { result: 1 } }
+    assert_equal 1, @transition.result
+  end
+end
diff --git a/test/unit/transition/transition_without_reading_state_test.rb b/test/unit/transition/transition_without_reading_state_test.rb
new file mode 100644
index 0000000..98f68d2
--- /dev/null
+++ b/test/unit/transition/transition_without_reading_state_test.rb
@@ -0,0 +1,22 @@
+require_relative '../../test_helper'
+
+class TransitionWithoutReadingStateTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    @machine = StateMachines::Machine.new(@klass)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state = 'idling'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling, false)
+  end
+
+  def test_should_not_read_from_value_from_object
+    assert_equal 'parked', @transition.from
+  end
+
+  def test_should_have_to_value
+    assert_equal 'idling', @transition.to
+  end
+end
diff --git a/test/unit/transition/transition_without_running_action_test.rb b/test/unit/transition/transition_without_running_action_test.rb
new file mode 100644
index 0000000..77bae96
--- /dev/null
+++ b/test/unit/transition/transition_without_running_action_test.rb
@@ -0,0 +1,47 @@
+require_relative '../../test_helper'
+
+class TransitionWithoutRunningActionTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :saved
+
+      def save
+        @saved = true
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, action: :save)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+    @machine.after_transition { |_object| @run_after = true }
+
+    @object = @klass.new
+    @object.state = 'parked'
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    @result = @transition.perform(false)
+  end
+
+  def test_should_have_empty_args
+    assert_equal [], @transition.args
+  end
+
+  def test_should_not_have_a_result
+    assert_nil @transition.result
+  end
+
+  def test_should_be_successful
+    assert_equal true, @result
+  end
+
+  def test_should_change_the_current_state
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_not_run_the_action
+    refute @object.saved
+  end
+
+  def test_should_run_after_callbacks
+    assert @run_after
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_by_default_test.rb b/test/unit/transition_collection/attribute_transition_collection_by_default_test.rb
new file mode 100644
index 0000000..a22b6fc
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_by_default_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionByDefaultTest < StateMachinesTest
+  def setup
+    @transitions = StateMachines::AttributeTransitionCollection.new
+  end
+
+  def test_should_skip_actions
+    assert @transitions.skip_actions
+  end
+
+  def test_should_not_skip_after
+    refute @transitions.skip_after
+  end
+
+  def test_should_not_use_transaction
+    refute @transitions.use_transactions
+  end
+
+  def test_should_be_empty
+    assert @transitions.empty?
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb b/test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb
new file mode 100644
index 0000000..846732a
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb
@@ -0,0 +1,64 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionMarshallingTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+    self.class.const_set('Example', @klass)
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+  end
+
+  def test_should_marshal_during_before_callbacks
+    @machine.before_transition { |object, _transition| Marshal.dump(object) }
+    transitions(after: false).perform { true }
+    transitions.perform { true }
+  end
+
+  def test_should_marshal_during_action
+    transitions(after: false).perform do
+      Marshal.dump(@object)
+      true
+    end
+
+    transitions.perform do
+      Marshal.dump(@object)
+      true
+    end
+  end
+
+  def test_should_marshal_during_after_callbacks
+    @machine.after_transition { |object, _transition| Marshal.dump(object) }
+    transitions(after: false).perform { true }
+    transitions.perform { true }
+  end
+
+  if StateMachines::Transition.pause_supported?
+    def test_should_marshal_during_around_callbacks_before_yield
+      @machine.around_transition { |object, _transition, block| Marshal.dump(object); block.call }
+      transitions(after: false).perform { true }
+      transitions.perform { true }
+    end
+
+    def test_should_marshal_during_around_callbacks_after_yield
+      @machine.around_transition { |object, _transition, block| block.call; Marshal.dump(object) }
+      transitions(after: false).perform { true }
+      transitions.perform { true }
+    end
+  end
+
+  def teardown
+    self.class.send(:remove_const, 'Example')
+  end
+
+  private
+  def transitions(options = {})
+    StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ], options)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb
new file mode 100644
index 0000000..0439792
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb
@@ -0,0 +1,44 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithActionErrorTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+    @object.status_event = 'shift_up'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+
+    begin
+      ; @transitions.perform { fail ArgumentError }
+    rescue
+    end
+  end
+
+  def test_should_not_persist_states
+    assert_equal 'parked', @object.state
+    assert_equal 'first_gear', @object.status
+  end
+
+  def test_should_not_clear_events
+    assert_equal :ignite, @object.state_event
+    assert_equal :shift_up, @object.status_event
+  end
+
+  def test_should_not_write_event_transitions
+    assert_nil @object.send(:state_event_transition)
+    assert_nil @object.send(:status_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb
new file mode 100644
index 0000000..238a49c
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb
@@ -0,0 +1,44 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithActionFailedTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+    @object.status_event = 'shift_up'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+    @result = @transitions.perform { false }
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @result
+  end
+
+  def test_should_not_persist_states
+    assert_equal 'parked', @object.state
+    assert_equal 'first_gear', @object.status
+  end
+
+  def test_should_not_clear_events
+    assert_equal :ignite, @object.state_event
+    assert_equal :shift_up, @object.status_event
+  end
+
+  def test_should_not_write_event_transitions
+    assert_nil @object.send(:state_event_transition)
+    assert_nil @object.send(:status_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb
new file mode 100644
index 0000000..eef418d
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithBeforeCallbackErrorTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.before_transition { fail ArgumentError }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    begin
+      ; @transitions.perform
+    rescue
+    end
+  end
+
+  def test_should_not_clear_event
+    assert_equal :ignite, @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb
new file mode 100644
index 0000000..a22cb4e
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithBeforeCallbackHaltTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.before_transition { throw :halt }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @result
+  end
+
+  def test_should_not_clear_event
+    assert_equal :ignite, @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb
new file mode 100644
index 0000000..c1cb5bd
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithAroundAfterYieldCallbackErrorTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.before_transition { fail ArgumentError }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    begin
+      ; @transitions.perform
+    rescue
+    end
+  end
+
+  def test_should_not_clear_event
+    assert_equal :ignite, @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb
new file mode 100644
index 0000000..a27f38a
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithAroundCallbackAfterYieldErrorTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.around_transition { |block| block.call; fail ArgumentError }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    begin
+      ; @transitions.perform
+    rescue
+    end
+  end
+
+  def test_should_clear_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb
new file mode 100644
index 0000000..6dcc4ba
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithAroundCallbackAfterYieldHaltTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.around_transition { |block| block.call; throw :halt }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_clear_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb
new file mode 100644
index 0000000..416e551
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithAroundCallbackBeforeYieldHaltTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.around_transition { throw :halt }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @result
+  end
+
+  def test_should_not_clear_event
+    assert_equal :ignite, @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb
new file mode 100644
index 0000000..22dca8c
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb
@@ -0,0 +1,32 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithAfterCallbackErrorTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.after_transition { fail ArgumentError }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    begin
+      ; @transitions.perform
+    rescue
+    end
+  end
+
+  def test_should_clear_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb
new file mode 100644
index 0000000..aa034a7
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb
@@ -0,0 +1,33 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithAfterCallbackHaltTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.after_transition { throw :halt }
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_clear_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb
new file mode 100644
index 0000000..7728c2e
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb
@@ -0,0 +1,68 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+  end
+
+  def test_should_not_have_events_during_before_callbacks
+    @state.before_transition { |object, _transition| @before_state_event = object.state_event }
+    @state.around_transition { |object, _transition, block| @around_state_event = object.state_event; block.call }
+    @transitions.perform
+
+    assert_nil @before_state_event
+    assert_nil @around_state_event
+  end
+
+  def test_should_not_have_events_during_action
+    @transitions.perform { @state_event = @object.state_event }
+
+    assert_nil @state_event
+  end
+
+  def test_should_not_have_events_during_after_callbacks
+    @state.after_transition { |object, _transition| @after_state_event = object.state_event }
+    @state.around_transition { |object, _transition, block| block.call; @around_state_event = object.state_event }
+    @transitions.perform
+
+    assert_nil @after_state_event
+    assert_nil @around_state_event
+  end
+
+  def test_should_not_have_event_transitions_during_before_callbacks
+    @state.before_transition { |object, _transition| @state_event_transition = object.send(:state_event_transition) }
+    @transitions.perform
+
+    assert_nil @state_event_transition
+  end
+
+  def test_should_not_have_event_transitions_during_action
+    @transitions.perform { @state_event_transition = @object.send(:state_event_transition) }
+
+    assert_nil @state_event_transition
+  end
+
+  def test_should_not_have_event_transitions_during_after_callbacks
+    @state.after_transition { |object, _transition| @after_state_event_transition = object.send(:state_event_transition) }
+    @state.around_transition { |object, _transition, block| block.call; @around_state_event_transition = object.send(:state_event_transition) }
+    @transitions.perform
+
+    assert_nil @after_state_event_transition
+    assert_nil @around_state_event_transition
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb
new file mode 100644
index 0000000..90bead4
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithEventTransitionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+    @object.send(:state_event_transition=, @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling))
+    @object.send(:status_event_transition=, @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear))
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([@state_transition, @status_transition])
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_not_write_events
+    assert_nil @object.state_event
+    assert_nil @object.status_event
+  end
+
+  def test_should_clear_event_transitions
+    assert_nil @object.send(:state_event_transition)
+    assert_nil @object.send(:status_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_events_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_events_test.rb
new file mode 100644
index 0000000..8068afd
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_events_test.rb
@@ -0,0 +1,44 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithEventsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+    @object.status_event = 'shift_up'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_clear_events
+    assert_nil @object.state_event
+    assert_nil @object.status_event
+  end
+
+  def test_should_not_write_event_transitions
+    assert_nil @object.send(:state_event_transition)
+    assert_nil @object.send(:status_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb b/test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb
new file mode 100644
index 0000000..0ac5178
--- /dev/null
+++ b/test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb
@@ -0,0 +1,42 @@
+require_relative '../../test_helper'
+
+class AttributeTransitionCollectionWithSkippedAfterCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+    @object.state_event = 'ignite'
+    @object.status_event = 'shift_up'
+
+    @transitions = StateMachines::AttributeTransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ], after: false)
+  end
+
+  def test_should_clear_events
+    @transitions.perform
+    assert_nil @object.state_event
+    assert_nil @object.status_event
+  end
+
+  def test_should_write_event_transitions_if_success
+    @transitions.perform { true }
+    assert_equal @state_transition, @object.send(:state_event_transition)
+    assert_equal @status_transition, @object.send(:status_event_transition)
+  end
+
+  def test_should_not_write_event_transitions_if_failed
+    @transitions.perform { false }
+    assert_nil @object.send(:state_event_transition)
+    assert_nil @object.send(:status_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_by_default_test.rb b/test/unit/transition_collection/transition_collection_by_default_test.rb
new file mode 100644
index 0000000..5fb69bd
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_by_default_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionByDefaultTest < StateMachinesTest
+  def setup
+    @transitions = StateMachines::TransitionCollection.new
+  end
+
+  def test_should_not_skip_actions
+    refute @transitions.skip_actions
+  end
+
+  def test_should_not_skip_after
+    refute @transitions.skip_after
+  end
+
+  def test_should_use_transaction
+    assert @transitions.use_transactions
+  end
+
+  def test_should_be_empty
+    assert @transitions.empty?
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_empty_with_block_test.rb b/test/unit/transition_collection/transition_collection_empty_with_block_test.rb
new file mode 100644
index 0000000..6b2fc25
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_empty_with_block_test.rb
@@ -0,0 +1,23 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionEmptyWithBlockTest < StateMachinesTest
+  def setup
+    @transitions = StateMachines::TransitionCollection.new
+  end
+
+  def test_should_raise_exception_if_perform_raises_exception
+    assert_raises(ArgumentError) { @transitions.perform { fail ArgumentError } }
+  end
+
+  def test_should_use_block_result_if_non_boolean
+    assert_equal 1, @transitions.perform { 1 }
+  end
+
+  def test_should_use_block_result_if_false
+    assert_equal false, @transitions.perform { false }
+  end
+
+  def test_should_use_block_reslut_if_nil
+    assert_equal nil, @transitions.perform { nil }
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_empty_without_block_test.rb b/test/unit/transition_collection/transition_collection_empty_without_block_test.rb
new file mode 100644
index 0000000..0467a9a
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_empty_without_block_test.rb
@@ -0,0 +1,12 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionEmptyWithoutBlockTest < StateMachinesTest
+  def setup
+    @transitions = StateMachines::TransitionCollection.new
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_invalid_test.rb b/test/unit/transition_collection/transition_collection_invalid_test.rb
new file mode 100644
index 0000000..2cc27d3
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_invalid_test.rb
@@ -0,0 +1,21 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionInvalidTest < StateMachinesTest
+  def setup
+    @transitions = StateMachines::TransitionCollection.new([false])
+  end
+
+  def test_should_be_empty
+    assert @transitions.empty?
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @transitions.perform
+  end
+
+  def test_should_not_run_perform_block
+    ran_block = false
+    @transitions.perform { ran_block = true }
+    refute ran_block
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_partial_invalid_test.rb b/test/unit/transition_collection/transition_collection_partial_invalid_test.rb
new file mode 100644
index 0000000..83eaf49
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_partial_invalid_test.rb
@@ -0,0 +1,69 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionPartialInvalidTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :ran_transaction
+    end
+
+    @callbacks = []
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :idling
+    @machine.event :ignite
+    @machine.before_transition { @callbacks << :before }
+    @machine.after_transition { @callbacks << :after }
+    @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after }
+
+    class << @machine
+      def within_transaction(object)
+        object.ran_transaction = true
+      end
+    end
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+      false
+    ])
+  end
+
+  def test_should_not_store_invalid_values
+    assert_equal 1, @transitions.length
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @transitions.perform
+  end
+
+  def test_should_not_start_transaction
+    refute @object.ran_transaction
+  end
+
+  def test_should_not_run_perform_block
+    ran_block = false
+    @transitions.perform { ran_block = true }
+    refute ran_block
+  end
+
+  def test_should_not_run_before_callbacks
+    refute @callbacks.include?(:before)
+  end
+
+  def test_should_not_persist_states
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_run_after_callbacks
+    refute @callbacks.include?(:after)
+  end
+
+  def test_should_not_run_around_callbacks_before_yield
+    refute @callbacks.include?(:around_before)
+  end
+
+  def test_should_not_run_around_callbacks_after_yield
+    refute @callbacks.include?(:around_after)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_test.rb b/test/unit/transition_collection/transition_collection_test.rb
new file mode 100644
index 0000000..29bad02
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_test.rb
@@ -0,0 +1,26 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionTest < StateMachinesTest
+  def test_should_raise_exception_if_invalid_option_specified
+    exception = assert_raises(ArgumentError) { StateMachines::TransitionCollection.new([], invalid: true) }
+    assert_equal 'Unknown key: :invalid. Valid keys are: :actions, :after, :use_transactions', exception.message
+  end
+
+  def test_should_raise_exception_if_multiple_transitions_for_same_attribute_specified
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :parked, :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+
+    exception = assert_raises(ArgumentError) do
+      StateMachines::TransitionCollection.new([
+        StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling),
+        StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+      ])
+    end
+    assert_equal 'Cannot perform multiple transitions in parallel for the same state machine attribute', exception.message
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_valid_test.rb b/test/unit/transition_collection/transition_collection_valid_test.rb
new file mode 100644
index 0000000..c127be8
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_valid_test.rb
@@ -0,0 +1,57 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionValidTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :persisted
+
+      def initialize
+        @persisted = nil
+        super
+        @persisted = []
+      end
+
+      def state=(value)
+        @persisted << 'state' if @persisted
+        @state = value
+      end
+
+      def status=(value)
+        @persisted << 'status' if @persisted
+        @status = value
+      end
+    end
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked)
+    @state.state :idling
+    @state.event :ignite
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+
+    @result = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ]).perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_persist_each_state
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_persist_in_order
+    assert_equal %w(state status), @object.persisted
+  end
+
+  def test_should_store_results_in_transitions
+    assert_nil @state_transition.result
+    assert_nil @status_transition.result
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_error_test.rb b/test/unit/transition_collection/transition_collection_with_action_error_test.rb
new file mode 100644
index 0000000..516d5e0
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_error_test.rb
@@ -0,0 +1,66 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithActionErrorTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+        fail ArgumentError
+      end
+    end
+    @before_count = 0
+    @around_before_count = 0
+    @after_count = 0
+    @around_after_count = 0
+    @failure_count = 0
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.before_transition { @before_count += 1 }
+    @machine.after_transition { @after_count += 1 }
+    @machine.around_transition { |block| @around_before_count += 1; block.call; @around_after_count += 1 }
+    @machine.after_failure { @failure_count += 1 }
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+
+    @raised = true
+    begin
+      @transitions.perform
+      @raised = false
+    rescue ArgumentError
+    end
+  end
+
+  def test_should_not_catch_exception
+    assert @raised
+  end
+
+  def test_should_not_persist_state
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_run_before_callbacks
+    assert_equal 1, @before_count
+  end
+
+  def test_should_run_around_callbacks_before_yield
+    assert_equal 1, @around_before_count
+  end
+
+  def test_should_not_run_after_callbacks
+    assert_equal 0, @after_count
+  end
+
+  def test_should_not_run_around_callbacks_after_yield
+    assert_equal 0, @around_after_count
+  end
+
+  def test_should_not_run_failure_callbacks
+    assert_equal 0, @failure_count
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_failed_test.rb b/test/unit/transition_collection/transition_collection_with_action_failed_test.rb
new file mode 100644
index 0000000..fe8f36b
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_failed_test.rb
@@ -0,0 +1,60 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithActionFailedTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+        false
+      end
+    end
+    @before_count = 0
+    @around_before_count = 0
+    @after_count = 0
+    @around_after_count = 0
+    @failure_count = 0
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.before_transition { @before_count += 1 }
+    @machine.after_transition { @after_count += 1 }
+    @machine.around_transition { |block| @around_before_count += 1; block.call; @around_after_count += 1 }
+    @machine.after_failure { @failure_count += 1 }
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @result
+  end
+
+  def test_should_not_persist_state
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_run_before_callbacks
+    assert_equal 1, @before_count
+  end
+
+  def test_should_run_around_callbacks_before_yield
+    assert_equal 1, @around_before_count
+  end
+
+  def test_should_not_run_after_callbacks
+    assert_equal 0, @after_count
+  end
+
+  def test_should_not_run_around_callbacks
+    assert_equal 0, @around_after_count
+  end
+
+  def test_should_run_failure_callbacks
+    assert_equal 1, @failure_count
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb
new file mode 100644
index 0000000..c12014a
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test'
+
+class TransitionCollectionWithActionHookAndBlockTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+    @result = StateMachines::TransitionCollection.new([@transition]).perform { true }
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_not_run_action
+    refute @object.saved
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb
new file mode 100644
index 0000000..3a8e690
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookAndSkippedActionTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+    @result = StateMachines::TransitionCollection.new([@transition], actions: false).perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_not_run_action
+    refute @object.saved
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb
new file mode 100644
index 0000000..2ba7bb2
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb
@@ -0,0 +1,37 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookAndSkippedAfterCallbacksTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+    @result = StateMachines::TransitionCollection.new([@transition], after: false).perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_run_action
+    assert @object.saved
+  end
+
+  def test_should_have_already_persisted_when_running_action
+    assert_equal 'idling', @object.state_on_save
+  end
+
+  def test_should_not_have_event_during_action
+    assert_nil @object.state_event_on_save
+  end
+
+  def test_should_not_write_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_have_event_transition_during_save
+    assert_nil @object.state_event_transition_on_save
+  end
+
+  def test_should_not_write_event_attribute
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb
new file mode 100644
index 0000000..c115122
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb
@@ -0,0 +1,34 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithActionHookBaseTest < StateMachinesTest
+  def setup
+    @superclass = Class.new do
+      def save
+        true
+      end
+    end
+
+    @klass = Class.new(@superclass) do
+      attr_reader :saved, :state_on_save, :state_event_on_save, :state_event_transition_on_save
+
+      def save
+        @saved = true
+        @state_on_save = state
+        @state_event_on_save = state_event
+        @state_event_transition_on_save = state_event_transition
+        super
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+
+    @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+  end
+
+  def default_test
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb
new file mode 100644
index 0000000..6c35c79
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookErrorTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+
+    @superclass.class_eval do
+      remove_method :save
+
+      def save
+        fail ArgumentError
+      end
+    end
+
+    begin
+      ; StateMachines::TransitionCollection.new([@transition]).perform
+    rescue
+    end
+  end
+
+  def test_should_not_write_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb
new file mode 100644
index 0000000..a6befab
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb
@@ -0,0 +1,17 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookInvalidTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+    @result = StateMachines::TransitionCollection.new([@transition, nil]).perform
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @result
+  end
+
+  def test_should_not_run_action
+    refute @object.saved
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb
new file mode 100644
index 0000000..de1d9b4
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb
@@ -0,0 +1,79 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookMultipleTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+
+    @status_machine = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status_machine.state :second_gear
+    @status_machine.event :shift_up
+
+    @klass.class_eval do
+      attr_reader :status_on_save, :status_event_on_save, :status_event_transition_on_save
+
+      remove_method :save
+
+      def save
+        @saved = true
+        @state_on_save = state
+        @state_event_on_save = state_event
+        @state_event_transition_on_save = state_event_transition
+        @status_on_save = status
+        @status_event_on_save = status_event
+        @status_event_transition_on_save = status_event_transition
+        super
+        1
+      end
+    end
+
+    @object = @klass.new
+    @state_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    @status_transition = StateMachines::Transition.new(@object, @status_machine, :shift_up, :first_gear, :second_gear)
+
+    @result = StateMachines::TransitionCollection.new([@state_transition, @status_transition]).perform
+  end
+
+  def test_should_succeed
+    assert_equal 1, @result
+  end
+
+  def test_should_run_action
+    assert @object.saved
+  end
+
+  def test_should_not_have_already_persisted_when_running_action
+    assert_equal 'parked', @object.state_on_save
+    assert_equal 'first_gear', @object.status_on_save
+  end
+
+  def test_should_persist
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_not_have_events_during_action
+    assert_nil @object.state_event_on_save
+    assert_nil @object.status_event_on_save
+  end
+
+  def test_should_not_write_events
+    assert_nil @object.state_event
+    assert_nil @object.status_event
+  end
+
+  def test_should_have_event_transitions_during_action
+    assert_equal @state_transition, @object.state_event_transition_on_save
+    assert_equal @status_transition, @object.status_event_transition_on_save
+  end
+
+  def test_should_not_write_event_transitions
+    assert_nil @object.send(:state_event_transition)
+    assert_nil @object.send(:status_event_transition)
+  end
+
+  def test_should_mark_event_transitions_as_transient
+    assert @state_transition.transient?
+    assert @status_transition.transient?
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_test.rb
new file mode 100644
index 0000000..433ef3b
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_test.rb
@@ -0,0 +1,45 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+    @result = StateMachines::TransitionCollection.new([@transition]).perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_run_action
+    assert @object.saved
+  end
+
+  def test_should_not_have_already_persisted_when_running_action
+    assert_equal 'parked', @object.state_on_save
+  end
+
+  def test_should_persist
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_not_have_event_during_action
+    assert_nil @object.state_event_on_save
+  end
+
+  def test_should_not_write_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_have_event_transition_during_action
+    assert_equal @transition, @object.state_event_transition_on_save
+  end
+
+  def test_should_not_write_event_transition
+    assert_nil @object.send(:state_event_transition)
+  end
+
+  def test_should_mark_event_transition_as_transient
+    assert @transition.transient?
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb
new file mode 100644
index 0000000..e5114c8
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb
@@ -0,0 +1,48 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookWithDifferentActionsTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+
+    @klass.class_eval do
+      def save_status
+        true
+      end
+    end
+
+    @machine = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save_status)
+    @machine.state :second_gear
+    @machine.event :shift_up
+
+    @result = StateMachines::TransitionCollection.new([@transition, StateMachines::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]).perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_run_action
+    assert @object.saved
+  end
+
+  def test_should_have_already_persisted_when_running_action
+    assert_equal 'idling', @object.state_on_save
+  end
+
+  def test_should_not_have_event_during_action
+    assert_nil @object.state_event_on_save
+  end
+
+  def test_should_not_write_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_have_event_transition_during_save
+    assert_nil @object.state_event_transition_on_save
+  end
+
+  def test_should_not_write_event_attribute
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb b/test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb
new file mode 100644
index 0000000..e8b9126
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb
@@ -0,0 +1,42 @@
+require_relative '../../test_helper'
+require_relative 'transition_collection_with_action_hook_base_test.rb'
+
+class TransitionCollectionWithActionHookWithNilActionTest < TransitionCollectionWithActionHookBaseTest
+  def setup
+    super
+
+    @machine = StateMachines::Machine.new(@klass, :status, initial: :first_gear)
+    @machine.state :second_gear
+    @machine.event :shift_up
+
+    @result = StateMachines::TransitionCollection.new([@transition, StateMachines::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]).perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_run_action
+    assert @object.saved
+  end
+
+  def test_should_have_already_persisted_when_running_action
+    assert_equal 'idling', @object.state_on_save
+  end
+
+  def test_should_not_have_event_during_action
+    assert_nil @object.state_event_on_save
+  end
+
+  def test_should_not_write_event
+    assert_nil @object.state_event
+  end
+
+  def test_should_not_have_event_transition_during_save
+    assert_nil @object.state_event_transition_on_save
+  end
+
+  def test_should_not_write_event_attribute
+    assert_nil @object.send(:state_event_transition)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb b/test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb
new file mode 100644
index 0000000..8f6cc23
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb
@@ -0,0 +1,51 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithBeforeCallbackHaltTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :saved
+
+      def save
+        @saved = true
+      end
+    end
+    @before_count = 0
+    @after_count = 0
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.before_transition { @before_count += 1; throw :halt }
+    @machine.before_transition { @before_count += 1 }
+    @machine.after_transition { @after_count += 1 }
+    @machine.around_transition { |block| @before_count += 1; block.call; @after_count += 1 }
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_not_succeed
+    assert_equal false, @result
+  end
+
+  def test_should_not_persist_state
+    assert_equal 'parked', @object.state
+  end
+
+  def test_should_not_run_action
+    refute @object.saved
+  end
+
+  def test_should_not_run_further_before_callbacks
+    assert_equal 1, @before_count
+  end
+
+  def test_should_not_run_after_callbacks
+    assert_equal 0, @after_count
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb b/test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb
new file mode 100644
index 0000000..f1718ac
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb
@@ -0,0 +1,47 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithAfterCallbackHaltTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :saved
+
+      def save
+        @saved = true
+      end
+    end
+    @before_count = 0
+    @after_count = 0
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @machine.before_transition { @before_count += 1 }
+    @machine.after_transition { @after_count += 1; throw :halt }
+    @machine.after_transition { @after_count += 1 }
+    @machine.around_transition { |block| @before_count += 1; block.call; @after_count += 1 }
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_persist_state
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_run_before_callbacks
+    assert_equal 2, @before_count
+  end
+
+  def test_should_not_run_further_after_callbacks
+    assert_equal 2, @after_count
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_block_test.rb b/test/unit/transition_collection/transition_collection_with_block_test.rb
new file mode 100644
index 0000000..8179b4e
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_block_test.rb
@@ -0,0 +1,46 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithBlockTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :actions
+
+      def save
+        (@actions ||= []) << :save
+      end
+    end
+
+    @state = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+    @transitions = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+    @result = @transitions.perform { 1 }
+  end
+
+  def test_should_succeed
+    assert_equal 1, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_not_run_machine_actions
+    assert_nil @object.actions
+  end
+
+  def test_should_use_result_as_transition_result
+    assert_equal 1, @state_transition.result
+    assert_equal 1, @status_transition.result
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_callbacks_test.rb b/test/unit/transition_collection/transition_collection_with_callbacks_test.rb
new file mode 100644
index 0000000..b0c5295
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_callbacks_test.rb
@@ -0,0 +1,135 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :saved
+
+      def save
+        @saved = true
+      end
+    end
+
+    @before_callbacks = []
+    @after_callbacks = []
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+    @state.before_transition { @before_callbacks << :state_before }
+    @state.after_transition { @after_callbacks << :state_after }
+    @state.around_transition { |block| @before_callbacks << :state_around; block.call; @after_callbacks << :state_around }
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+    @status.before_transition { @before_callbacks << :status_before }
+    @status.after_transition { @after_callbacks << :status_after }
+    @status.around_transition { |block| @before_callbacks << :status_around; block.call; @after_callbacks << :status_around }
+
+    @object = @klass.new
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+  end
+
+  def test_should_run_before_callbacks_in_order
+    @transitions.perform
+    assert_equal [:state_before, :state_around, :status_before, :status_around], @before_callbacks
+  end
+
+  def test_should_halt_if_before_callback_halted_for_first_transition
+    @state.before_transition { throw :halt }
+
+    assert_equal false, @transitions.perform
+    assert_equal [:state_before, :state_around], @before_callbacks
+  end
+
+  def test_should_halt_if_before_callback_halted_for_second_transition
+    @status.before_transition { throw :halt }
+
+    assert_equal false, @transitions.perform
+    assert_equal [:state_before, :state_around, :status_before, :status_around], @before_callbacks
+  end
+
+  def test_should_halt_if_around_callback_halted_before_yield_for_first_transition
+    @state.around_transition { throw :halt }
+
+    assert_equal false, @transitions.perform
+    assert_equal [:state_before, :state_around], @before_callbacks
+  end
+
+  def test_should_halt_if_around_callback_halted_before_yield_for_second_transition
+    @status.around_transition { throw :halt }
+
+    assert_equal false, @transitions.perform
+    assert_equal [:state_before, :state_around, :status_before, :status_around], @before_callbacks
+  end
+
+  def test_should_run_after_callbacks_in_reverse_order
+    @transitions.perform
+    assert_equal [:status_around, :status_after, :state_around, :state_after], @after_callbacks
+  end
+
+  def test_should_not_halt_if_after_callback_halted_for_first_transition
+    @state.after_transition { throw :halt }
+
+    assert_equal true, @transitions.perform
+    assert_equal [:status_around, :status_after, :state_around, :state_after], @after_callbacks
+  end
+
+  def test_should_not_halt_if_around_callback_halted_for_second_transition
+    @status.around_transition { |block| block.call; throw :halt }
+
+    assert_equal true, @transitions.perform
+    assert_equal [:state_around, :state_after], @after_callbacks
+  end
+
+  def test_should_run_before_callbacks_before_persisting_the_state
+    @state.before_transition { |object| @before_state = object.state }
+    @state.around_transition { |object, _transition, block| @around_state = object.state; block.call }
+    @transitions.perform
+
+    assert_equal 'parked', @before_state
+    assert_equal 'parked', @around_state
+  end
+
+  def test_should_persist_state_before_running_action
+    @klass.class_eval do
+      attr_reader :saved_on_persist
+
+      def state=(value)
+        @state = value
+        @saved_on_persist = saved
+      end
+    end
+
+    @transitions.perform
+    refute @object.saved_on_persist
+  end
+
+  def test_should_persist_state_before_running_action_block
+    @klass.class_eval do
+      attr_writer :saved
+      attr_reader :saved_on_persist
+
+      def state=(value)
+        @state = value
+        @saved_on_persist = saved
+      end
+    end
+
+    @transitions.perform { @object.saved = true }
+    refute @object.saved_on_persist
+  end
+
+  def test_should_run_after_callbacks_after_running_the_action
+    @state.after_transition { |object| @after_saved = object.saved }
+    @state.around_transition { |object, _transition, block| block.call; @around_saved = object.saved }
+    @transitions.perform
+
+    assert @after_saved
+    assert @around_saved
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_different_actions_test.rb b/test/unit/transition_collection/transition_collection_with_different_actions_test.rb
new file mode 100644
index 0000000..98b62b1
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_different_actions_test.rb
@@ -0,0 +1,189 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithDifferentActionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :actions
+
+      def save_state
+        (@actions ||= []) << :save_state
+        :save_state
+      end
+
+      def save_status
+        (@actions ||= []) << :save_status
+        :save_status
+      end
+    end
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save_state)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save_status)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+  end
+
+  def test_should_succeed
+    assert_equal true, @transitions.perform
+  end
+
+  def test_should_persist_states
+    @transitions.perform
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_run_actions_in_order
+    @transitions.perform
+    assert_equal [:save_state, :save_status], @object.actions
+  end
+
+  def test_should_store_results_in_transitions
+    @transitions.perform
+    assert_equal :save_state, @state_transition.result
+    assert_equal :save_status, @status_transition.result
+  end
+
+  def test_should_not_halt_if_action_fails_for_first_transition
+    @klass.class_eval do
+      remove_method :save_state
+
+      def save_state
+        (@actions ||= []) << :save_state
+        false
+      end
+    end
+
+    assert_equal false, @transitions.perform
+    assert_equal [:save_state, :save_status], @object.actions
+  end
+
+  def test_should_halt_if_action_fails_for_second_transition
+    @klass.class_eval do
+      remove_method :save_status
+
+      def save_status
+        (@actions ||= []) << :save_status
+        false
+      end
+    end
+
+    assert_equal false, @transitions.perform
+    assert_equal [:save_state, :save_status], @object.actions
+  end
+
+  def test_should_rollback_if_action_errors_for_first_transition
+    @klass.class_eval do
+      remove_method :save_state
+
+      def save_state
+        fail ArgumentError
+      end
+    end
+
+    begin
+      ; @transitions.perform
+    rescue
+    end
+    assert_equal 'parked', @object.state
+    assert_equal 'first_gear', @object.status
+  end
+
+  def test_should_rollback_if_action_errors_for_second_transition
+    @klass.class_eval do
+      remove_method :save_status
+
+      def save_status
+        fail ArgumentError
+      end
+    end
+
+    begin
+      ; @transitions.perform
+    rescue
+    end
+    assert_equal 'parked', @object.state
+    assert_equal 'first_gear', @object.status
+  end
+
+  def test_should_not_run_after_callbacks_if_action_fails_for_first_transition
+    @klass.class_eval do
+      remove_method :save_state
+
+      def save_state
+        false
+      end
+    end
+
+    @callbacks = []
+    @state.after_transition { @callbacks << :state_after }
+    @state.around_transition { |block| block.call; @callbacks << :state_around }
+    @status.after_transition { @callbacks << :status_after }
+    @status.around_transition { |block| block.call; @callbacks << :status_around }
+
+    @transitions.perform
+    assert_equal [], @callbacks
+  end
+
+  def test_should_not_run_after_callbacks_if_action_fails_for_second_transition
+    @klass.class_eval do
+      remove_method :save_status
+
+      def save_status
+        false
+      end
+    end
+
+    @callbacks = []
+    @state.after_transition { @callbacks << :state_after }
+    @state.around_transition { |block| block.call; @callbacks << :state_around }
+    @status.after_transition { @callbacks << :status_after }
+    @status.around_transition { |block| block.call; @callbacks << :status_around }
+
+    @transitions.perform
+    assert_equal [], @callbacks
+  end
+
+  def test_should_run_after_failure_callbacks_if_action_fails_for_first_transition
+    @klass.class_eval do
+      remove_method :save_state
+
+      def save_state
+        false
+      end
+    end
+
+    @callbacks = []
+    @state.after_failure { @callbacks << :state_after }
+    @status.after_failure { @callbacks << :status_after }
+
+    @transitions.perform
+    assert_equal [:status_after, :state_after], @callbacks
+  end
+
+  def test_should_run_after_failure_callbacks_if_action_fails_for_second_transition
+    @klass.class_eval do
+      remove_method :save_status
+
+      def save_status
+        false
+      end
+    end
+
+    @callbacks = []
+    @state.after_failure { @callbacks << :state_after }
+    @status.after_failure { @callbacks << :status_after }
+
+    @transitions.perform
+    assert_equal [:status_after, :state_after], @callbacks
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb b/test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb
new file mode 100644
index 0000000..72130fc
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb
@@ -0,0 +1,48 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithDuplicateActionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :actions
+
+      def save
+        (@actions ||= []) << :save
+        :save
+      end
+    end
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal :save, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_run_action_once
+    assert_equal [:save], @object.actions
+  end
+
+  def test_should_store_results_in_transitions
+    assert_equal :save, @state_transition.result
+    assert_equal :save, @status_transition.result
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_empty_actions_test.rb b/test/unit/transition_collection/transition_collection_with_empty_actions_test.rb
new file mode 100644
index 0000000..bb4176b
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_empty_actions_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithEmptyActionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+
+    @object.state = 'idling'
+    @object.status = 'second_gear'
+
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_store_results_in_transitions
+    assert_nil @state_transition.result
+    assert_nil @status_transition.result
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb b/test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb
new file mode 100644
index 0000000..c2c17ab
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb
@@ -0,0 +1,41 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithMixedActionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      def save
+        true
+      end
+    end
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save)
+    @state.state :idling
+    @state.event :ignite
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear)
+    @status.state :second_gear
+    @status.event :shift_up
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ])
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_store_results_in_transitions
+    assert_equal true, @state_transition.result
+    assert_nil @status_transition.result
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb b/test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb
new file mode 100644
index 0000000..3976322
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb
@@ -0,0 +1,34 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithSkippedActionsAndBlockTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked, action: :save_state)
+    @machine.state :idling
+    @machine.event :ignite
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ], actions: false)
+    @result = @transitions.perform { @ran_block = true; 1 }
+  end
+
+  def test_should_succeed
+    assert_equal 1, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+  end
+
+  def test_should_run_block
+    assert @ran_block
+  end
+
+  def test_should_store_results_in_transitions
+    assert_equal 1, @state_transition.result
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb b/test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb
new file mode 100644
index 0000000..b87aec9
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb
@@ -0,0 +1,69 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithSkippedActionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_reader :actions
+
+      def save_state
+        (@actions ||= []) << :save_state
+        :save_state
+      end
+
+      def save_status
+        (@actions ||= []) << :save_status
+        :save_status
+      end
+    end
+
+    @callbacks = []
+
+    @state = StateMachines::Machine.new(@klass, initial: :parked, action: :save_state)
+    @state.state :idling
+    @state.event :ignite
+    @state.before_transition { @callbacks << :state_before }
+    @state.after_transition { @callbacks << :state_after }
+    @state.around_transition { |block| @callbacks << :state_around_before; block.call; @callbacks << :state_around_after }
+
+    @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save_status)
+    @status.state :second_gear
+    @status.event :shift_up
+    @status.before_transition { @callbacks << :status_before }
+    @status.after_transition { @callbacks << :status_after }
+    @status.around_transition { |block| @callbacks << :status_around_before; block.call; @callbacks << :status_around_after }
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @state_transition = StateMachines::Transition.new(@object, @state, :ignite, :parked, :idling),
+      @status_transition = StateMachines::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
+    ], actions: false)
+    @result = @transitions.perform
+  end
+
+  def test_should_skip_actions
+    assert_equal true, @transitions.skip_actions
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_persist_states
+    assert_equal 'idling', @object.state
+    assert_equal 'second_gear', @object.status
+  end
+
+  def test_should_not_run_actions
+    assert_nil @object.actions
+  end
+
+  def test_should_store_results_in_transitions
+    assert_nil @state_transition.result
+    assert_nil @status_transition.result
+  end
+
+  def test_should_run_all_callbacks
+    assert_equal [:state_before, :state_around_before, :status_before, :status_around_before, :status_around_after, :status_after, :state_around_after, :state_after], @callbacks
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb b/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb
new file mode 100644
index 0000000..3ec7244
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb
@@ -0,0 +1,53 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithSkippedAfterCallbacksAndAroundCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @callbacks = []
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :idling
+    @machine.event :ignite
+    @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after }
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ], after: false)
+    @result = @transitions.perform
+  end
+
+  def test_should_raise_exception
+    skip('Not supported') if StateMachines::Transition.pause_supported?
+    assert_raises(ArgumentError) { @transitions.perform }
+  end
+
+  def test_should_succeed
+    skip unless StateMachines::Transition.pause_supported?
+
+    assert_equal true, @result
+  end
+
+  def test_should_not_run_around_callbacks_after_yield
+    skip unless StateMachines::Transition.pause_supported?
+
+    refute @callbacks.include?(:around_after)
+  end
+
+  def test_should_run_around_callbacks_after_yield_on_subsequent_perform
+    skip unless StateMachines::Transition.pause_supported?
+
+    StateMachines::TransitionCollection.new([@transition]).perform
+    assert @callbacks.include?(:around_after)
+  end
+
+  def test_should_not_rerun_around_callbacks_before_yield_on_subsequent_perform
+    skip unless StateMachines::Transition.pause_supported?
+
+    @callbacks = []
+    StateMachines::TransitionCollection.new([@transition]).perform
+    refute @callbacks.include?(:around_before)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb b/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb
new file mode 100644
index 0000000..feac90c
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb
@@ -0,0 +1,34 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithSkippedAfterCallbacksTest < StateMachinesTest
+  def setup
+    @klass = Class.new
+
+    @callbacks = []
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :idling
+    @machine.event :ignite
+    @machine.after_transition { @callbacks << :after }
+
+    @object = @klass.new
+
+    @transitions = StateMachines::TransitionCollection.new([
+      @transition = StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ], after: false)
+    @result = @transitions.perform
+  end
+
+  def test_should_succeed
+    assert_equal true, @result
+  end
+
+  def test_should_not_run_after_callbacks
+    refute @callbacks.include?(:after)
+  end
+
+  def test_should_run_after_callbacks_on_subsequent_perform
+    StateMachines::TransitionCollection.new([@transition]).perform
+    assert @callbacks.include?(:after)
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_with_transactions_test.rb b/test/unit/transition_collection/transition_collection_with_transactions_test.rb
new file mode 100644
index 0000000..dd1e64d
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_with_transactions_test.rb
@@ -0,0 +1,65 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithTransactionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :running_transaction, :cancelled_transaction
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :idling
+    @machine.event :ignite
+
+    class << @machine
+      def within_transaction(object)
+        object.running_transaction = true
+        object.cancelled_transaction = yield == false
+        object.running_transaction = false
+      end
+    end
+
+    @object = @klass.new
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ], use_transactions: true)
+  end
+
+  def test_should_run_before_callbacks_within_transaction
+    @machine.before_transition { |object| @in_transaction = object.running_transaction }
+    @transitions.perform
+
+    assert @in_transaction
+  end
+
+  def test_should_run_action_within_transaction
+    @transitions.perform { @in_transaction = @object.running_transaction }
+
+    assert @in_transaction
+  end
+
+  def test_should_run_after_callbacks_within_transaction
+    @machine.after_transition { |object| @in_transaction = object.running_transaction }
+    @transitions.perform
+
+    assert @in_transaction
+  end
+
+  def test_should_cancel_the_transaction_on_before_halt
+    @machine.before_transition { throw :halt }
+
+    @transitions.perform
+    assert @object.cancelled_transaction
+  end
+
+  def test_should_cancel_the_transaction_on_action_failure
+    @transitions.perform { false }
+    assert @object.cancelled_transaction
+  end
+
+  def test_should_not_cancel_the_transaction_on_after_halt
+    @machine.after_transition { throw :halt }
+
+    @transitions.perform
+    refute @object.cancelled_transaction
+  end
+end
diff --git a/test/unit/transition_collection/transition_collection_without_transactions_test.rb b/test/unit/transition_collection/transition_collection_without_transactions_test.rb
new file mode 100644
index 0000000..9b20e1a
--- /dev/null
+++ b/test/unit/transition_collection/transition_collection_without_transactions_test.rb
@@ -0,0 +1,29 @@
+require_relative '../../test_helper'
+
+class TransitionCollectionWithoutTransactionsTest < StateMachinesTest
+  def setup
+    @klass = Class.new do
+      attr_accessor :ran_transaction
+    end
+
+    @machine = StateMachines::Machine.new(@klass, initial: :parked)
+    @machine.state :idling
+    @machine.event :ignite
+
+    class << @machine
+      def within_transaction(object)
+        object.ran_transaction = true
+      end
+    end
+
+    @object = @klass.new
+    @transitions = StateMachines::TransitionCollection.new([
+      StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling)
+    ], use_transactions: false)
+    @transitions.perform
+  end
+
+  def test_should_not_run_within_transaction
+    refute @object.ran_transaction
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-state-machines.git



More information about the Pkg-ruby-extras-commits mailing list