Skip to content

Commit

Permalink
Add block syntax for sequences
Browse files Browse the repository at this point in the history
All expectations defined within the block are constrained by the
sequence.

For example, the following requires that Egg#crack must be called 1st,
Egg#fry must be called 2nd, and Egg#eat must be called 3rd:

    egg = mock('egg')
    sequence('breakfast') do
      egg.expects(:crack)
      egg.expects(:fry)
      egg.expects(:eat)
    end

Closes #61 (only 11 years late!)
  • Loading branch information
floehopper committed Nov 18, 2023
1 parent f798df8 commit 93fdffd
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 6 deletions.
21 changes: 19 additions & 2 deletions lib/mocha/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ def stub_everything(*arguments)

# Builds a new sequence which can be used to constrain the order in which expectations can occur.
#
# Specify that an expected invocation must occur within a named {Sequence} by using {Expectation#in_sequence}.
# Specify that an expected invocation must occur within a named {Sequence} by calling {Expectation#in_sequence}
# on each expectation or by passing a block within which all expectations should be constrained by the {Sequence}.
#
# @param [String] name name of sequence
# @yield optional block within which expectations should be constrained by the sequence
# @return [Sequence] a new sequence
#
# @see Expectation#in_sequence
Expand All @@ -157,8 +159,23 @@ def stub_everything(*arguments)
#
# task_one.execute
# task_two.execute
#
# @example Ensure methods on egg are invoked in the correct order using a block.
# egg = mock('egg')
# sequence('breakfast') do
# egg.expects(:crack)
# egg.expects(:fry)
# egg.expects(:eat)
# end
def sequence(name)
Sequence.new(name)
Sequence.new(name).tap do |seq|
Mockery.instance.sequences.push(seq)
begin
yield if block_given?
ensure
Mockery.instance.sequences.pop
end
end
end

# Builds a new state machine which can be used to constrain the order in which expectations can occur.
Expand Down
2 changes: 2 additions & 0 deletions lib/mocha/mock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def expects(method_name_or_hash, backtrace = nil)
method_name = args.shift
ensure_method_not_already_defined(method_name)
expectation = Expectation.new(self, method_name, backtrace)
expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
expectation.returns(args.shift) unless args.empty?
@expectations.add(expectation)
end
Expand Down Expand Up @@ -153,6 +154,7 @@ def stubs(method_name_or_hash, backtrace = nil)
ensure_method_not_already_defined(method_name)
expectation = Expectation.new(self, method_name, backtrace)
expectation.at_least(0)
expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
expectation.returns(args.shift) unless args.empty?
@expectations.add(expectation)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/mocha/mockery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ def state_machines
@state_machines ||= []
end

def sequences
@sequences ||= []
end

def mocha_inspect
message = ''
message << "unsatisfied expectations:\n- #{unsatisfied_expectations.map(&:mocha_inspect).join("\n- ")}\n" if unsatisfied_expectations.any?
Expand Down
182 changes: 182 additions & 0 deletions test/acceptance/sequence_block_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
require File.expand_path('../acceptance_test_helper', __FILE__)

class SequenceBlockTest < Mocha::TestCase
include AcceptanceTest

def setup
setup_acceptance_test
end

def teardown
teardown_acceptance_test
end

def test_should_constrain_invocations_to_occur_in_expected_order
test_result = run_as_test do
mock = mock()

sequence('one') do
mock.expects(:first)
mock.expects(:second)
end

mock.second
mock.first
end
assert_failed(test_result)
end

def test_should_allow_invocations_in_sequence
test_result = run_as_test do
mock = mock()

sequence('one') do
mock.expects(:first)
mock.expects(:second)
end

mock.first
mock.second
end
assert_passed(test_result)
end

def test_should_constrain_invocations_to_occur_in_expected_order_even_if_expected_on_different_mocks
test_result = run_as_test do
mock_one = mock('1')
mock_two = mock('2')

sequence('one') do
mock_one.expects(:first)
mock_two.expects(:second)
end

mock_two.second
mock_one.first
end
assert_failed(test_result)
end

def test_should_allow_invocations_in_sequence_even_if_expected_on_different_mocks
test_result = run_as_test do
mock_one = mock('1')
mock_two = mock('2')

sequence('one') do
mock_one.expects(:first)
mock_two.expects(:second)
end

mock_one.first
mock_two.second
end
assert_passed(test_result)
end

def test_should_constrain_invocations_to_occur_in_expected_order_even_if_expected_on_partial_mocks
test_result = run_as_test do
partial_mock_one = '1'
partial_mock_two = '2'

sequence('one') do
partial_mock_one.expects(:first)
partial_mock_two.expects(:second)
end

partial_mock_two.second
partial_mock_one.first
end
assert_failed(test_result)
end

def test_should_allow_invocations_in_sequence_even_if_expected_on_partial_mocks
test_result = run_as_test do
partial_mock_one = '1'
partial_mock_two = '2'

sequence('one') do
partial_mock_one.expects(:first)
partial_mock_two.expects(:second)
end

partial_mock_one.first
partial_mock_two.second
end
assert_passed(test_result)
end

def test_should_allow_stub_expectations_to_be_skipped_in_sequence
test_result = run_as_test do
mock = mock()

sequence('one') do
mock.expects(:first)
mock.stubs(:second)
mock.expects(:third)
end

mock.first
mock.third
end
assert_passed(test_result)
end

def test_should_regard_nested_sequences_as_independent_of_each_other
test_result = run_as_test do
mock = mock()

sequence('one') do
mock.expects(:first)
mock.expects(:second)

sequence('two') do
mock.expects(:third)
mock.expects(:fourth)
end
end

mock.first
mock.third
mock.second
mock.fourth
end
assert_passed(test_result)
end

def test_should_include_sequence_in_failure_message
test_result = run_as_test do
mock = mock()

sequence('one') do
mock.expects(:first)
mock.expects(:second)
end

mock.second
mock.first
end
assert_failed(test_result)
assert_match Regexp.new(%(in sequence "one")), test_result.failures.first.message
end

def test_should_allow_expectations_to_be_in_more_than_one_sequence
test_result = run_as_test do
mock = mock()
sequence_one = sequence('one')

mock.expects(:first).in_sequence(sequence_one)

sequence('two') do
mock.expects(:second)
mock.expects(:third).in_sequence(sequence_one)
end

mock.first
mock.third
mock.second
end
assert_failed(test_result)
assert_match Regexp.new(%(in sequence "one")), test_result.failures.first.message
assert_match Regexp.new(%(in sequence "two")), test_result.failures.first.message
end
end
3 changes: 2 additions & 1 deletion test/unit/any_instance_method_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'mocha/ruby_version'
require 'method_definer'
require 'mocha/class_methods'
require 'mocha/mockery'
require 'mocha/mock'
require 'mocha/any_instance_method'

Expand Down Expand Up @@ -130,6 +131,6 @@ def test_should_return_any_instance_mocha_for_stubbee
private

def build_mock
Mock.new(nil)
Mock.new(Mockery.new)
end
end
3 changes: 2 additions & 1 deletion test/unit/central_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require File.expand_path('../../test_helper', __FILE__)

require 'mocha/central'
require 'mocha/mockery'
require 'mocha/mock'

class CentralTest < Mocha::TestCase
Expand Down Expand Up @@ -93,6 +94,6 @@ def test_should_unstub_all_methods
private

def build_mock
Mock.new(nil)
Mock.new(Mockery.new)
end
end
3 changes: 2 additions & 1 deletion test/unit/instance_method_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'mocha/ruby_version'
require 'method_definer'
require 'mocha/class_methods'
require 'mocha/mockery'
require 'mocha/mock'

require 'mocha/instance_method'
Expand Down Expand Up @@ -238,6 +239,6 @@ def equal?(_other)
private

def build_mock
Mock.new(nil)
Mock.new(Mockery.new)
end
end
3 changes: 2 additions & 1 deletion test/unit/mock_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require File.expand_path('../../test_helper', __FILE__)
require 'mocha/macos_version'
require 'mocha/mockery'
require 'mocha/mock'
require 'mocha/expectation_error_factory'
require 'set'
Expand Down Expand Up @@ -350,6 +351,6 @@ def test_expectation_is_defined_on_mock
private

def build_mock
Mock.new(nil)
Mock.new(Mockery.new)
end
end

0 comments on commit 93fdffd

Please sign in to comment.