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:

    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 51690b2
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 3 deletions.
23 changes: 20 additions & 3 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 using {Expectation#in_sequence} or
# pass a block within which 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
def sequence(name)
Sequence.new(name)
#
# @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, &block)
Sequence.new(name).tap do |seq|
Mockery.instance.sequences.push(seq)
begin
block.call 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
6 changes: 6 additions & 0 deletions lib/mocha/mock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ 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)
if Mockery.instance.sequences.any?
expectation.in_sequence(Mockery.instance.sequences.last)
end
expectation.returns(args.shift) unless args.empty?
@expectations.add(expectation)
end
Expand Down Expand Up @@ -153,6 +156,9 @@ 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)
if Mockery.instance.sequences.any?
expectation.in_sequence(Mockery.instance.sequences.last)
end
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

0 comments on commit 51690b2

Please sign in to comment.