Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding compound methods to receive and have_received, fixes #1298. #1299

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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