Skip to content

Commit

Permalink
Adding compound methods to receive and have_received, fixes rspec#1298.
Browse files Browse the repository at this point in the history
  • Loading branch information
eLod committed Nov 18, 2019
1 parent 5b897e8 commit 4f70d98
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 0 deletions.
80 changes: 80 additions & 0 deletions features/basics/expecting_messages.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Feature: Expecting messages
message expectations trigger failures when the example completes. You can also use
`expect(...).not_to receive(...)` to set a negative message expectation.

Note: Composing expectations as shown here will only work if you are using rspec-expectations.

Scenario: Failing positive message expectation
Given a file named "unfulfilled_message_expectation_spec.rb" with:
"""ruby
Expand Down Expand Up @@ -73,3 +75,81 @@ Feature: Expecting messages
"""
When I run `rspec negative_message_expectation_spec.rb`
Then the examples should all pass

Scenario: Composing expectations with `.and`
Given a file named "and_expectations_spec.rb" with:
"""ruby
RSpec.describe "Composed expectations with `.and`" do
let(:dbl) { double("Some Collaborator") }
before do
allow(dbl).to receive_messages(foo: nil, bar: nil)
expect(dbl).to receive(:foo)
.and receive(:bar)
end
it "passes if both messages received" do
dbl.foo
dbl.bar
end
it "fails if only first message received" do
dbl.foo
end
it "fails if only second message received" do
dbl.bar
end
end
"""
When I run `rspec and_expectations_spec.rb`
Then it should fail with the following output:
| 3 examples, 2 failures |
| |
| 1) Composed expectations with `.and` fails if only first message received |
| Failure/Error: |
| expect(dbl).to receive(:foo) |
| .and receive(:bar) |
| |
| (Double "Some Collaborator").bar(*(any args)) |
| expected: 1 time with any arguments |
| received: 0 times with any arguments |
| |
| 2) Composed expectations with `.and` fails if only second message received |
| Failure/Error: |
| expect(dbl).to receive(:foo) |
| .and receive(:bar) |
| |
| (Double "Some Collaborator").foo(*(any args)) |
| expected: 1 time with any arguments |
| received: 0 times with any arguments |
| |

Scenario: Composing expectations with `.or`
Given a file named "or_expectations_spec.rb" with:
"""ruby
RSpec.describe "Composed expectations with `.or`" do
let(:dbl) { double("Some Collaborator") }
before do
allow(dbl).to receive_messages(foo: nil, bar: nil)
expect(dbl).to receive(:foo)
.or receive(:bar)
end
it "passes if both messages received" do
dbl.foo
dbl.bar
end
it "passes if only first message received" do
dbl.foo
end
it "passes if only second message received" do
dbl.bar
end
end
"""
When I run `rspec or_expectations_spec.rb`
Then the examples should all pass
3 changes: 3 additions & 0 deletions lib/rspec/mocks/matchers/have_received.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Matchers
# @private
class HaveReceived
include Matcher
if defined? RSpec::Matchers::Composable
include RSpec::Matchers::Composable
end

COUNT_CONSTRAINTS = %w[exactly at_least at_most times time once twice thrice]
ARGS_CONSTRAINTS = %w[with]
Expand Down
3 changes: 3 additions & 0 deletions lib/rspec/mocks/matchers/receive.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ module Matchers
# @private
class Receive
include Matcher
if defined? RSpec::Matchers::Composable
include RSpec::Matchers::Composable
end

def initialize(message, block)
@message = message
Expand Down
11 changes: 11 additions & 0 deletions spec/rspec/mocks/matchers/have_received_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'rspec/mocks/matchers/shared_examples'

module RSpec
module Mocks
# This shared example group is highly unusual as it is used to test how
Expand Down Expand Up @@ -670,6 +672,15 @@ def fail_including(*snippets)
raise_error(RSpec::Expectations::ExpectationNotMetError, a_string_including(*snippets))
end
end

it_behaves_like "supports compound expectations" do
def verify
set_expectation
end

let(:left_expectation) { have_received(:foo) }
let(:right_expectation) { have_received(:bar) }
end
end

RSpec.describe Matchers::HaveReceived, "when used in a context that has only rspec-mocks available" do
Expand Down
19 changes: 19 additions & 0 deletions spec/rspec/mocks/matchers/receive_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'rspec/mocks/matchers/shared_examples'

module RSpec
module Mocks
RSpec.describe Matchers::Receive do
Expand Down Expand Up @@ -570,6 +572,19 @@ def eq(_)
after { RSpec::Mocks.configuration.syntax = :expect }
end

shared_examples 'supports compound expectations with rspec-expectations' do
it_behaves_like 'supports compound expectations' do
def verify
verify_all
end

let(:left_expectation) { receive(:foo) }
let(:right_expectation) { receive(:bar) }

before { set_expectation }
end
end

context "when rspec-expectations is included in the test framework first" do
before do
# the examples here assume `expect` is define in RSpec::Matchers...
Expand All @@ -590,6 +605,8 @@ def eq(_)
expect(3).to eq(3)
end
end

include_examples "supports compound expectations with rspec-expectations"
end

context "when rspec-expectations is included in the test framework last" do
Expand All @@ -612,6 +629,8 @@ def eq(_)
expect(3).to eq(3)
end
end

include_examples "supports compound expectations with rspec-expectations"
end
end
end
Expand Down
60 changes: 60 additions & 0 deletions spec/rspec/mocks/matchers/shared_examples.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
RSpec.shared_context "with compound expectation" do
let(:dbl) { double }

before { allow(dbl).to receive_messages(foo: 2, bar: 3) }

subject(:set_expectation) do
expect(dbl).to left_expectation
.public_send compound_method, right_expectation
end
end

RSpec.shared_examples "supports compound and expectation" do
let(:compound_method) { :and }

it "passes if both messages received" do
dbl.foo
dbl.bar
expect { verify }.not_to raise_error
end

it "fails if only first message received" do
dbl.foo
expect { verify }.to raise_error(/bar\(\*\(any args\)\).*expected: 1 time.*received: 0 times/m)
end

it "fails if only second message received" do
dbl.bar
expect { verify }.to raise_error(/foo\(\*\(any args\)\).*expected: 1 time.*received: 0 times/m)
end
end

RSpec.shared_examples "supports compound or expectation" do
let(:compound_method) { :or }

it "passes if both messages received" do
dbl.foo
dbl.bar
expect { verify }.not_to raise_error
end

it "passes if only first message received" do
dbl.foo
expect { verify }.not_to raise_error
end

it "passes if only second message received" do
dbl.bar
expect { verify }.not_to raise_error
end
end

RSpec.shared_examples "supports compound expectations" do
include_context "with compound expectation"

%i[and or].each do |method|
context "with `.#{method}`" do
include_examples "supports compound #{method} expectation"
end
end
end

0 comments on commit 4f70d98

Please sign in to comment.