From 7e09016e5c9b380e251a5b59e8a9022bba56f99c Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 14 Jul 2022 20:46:52 -0600 Subject: [PATCH] Add count modifiers for have_received matcher --- spec/matchers/receive_matcher_spec.cr | 201 ++++++++++++++++++ .../spectator/dsl/mocks/have_received_spec.cr | 180 ++++++++++++++++ src/spectator/matchers/receive_matcher.cr | 54 +++++ 3 files changed, 435 insertions(+) diff --git a/spec/matchers/receive_matcher_spec.cr b/spec/matchers/receive_matcher_spec.cr index d69bb5c..6dfdd28 100644 --- a/spec/matchers/receive_matcher_spec.cr +++ b/spec/matchers/receive_matcher_spec.cr @@ -311,4 +311,205 @@ Spectator.describe Spectator::Matchers::ReceiveMatcher do end end end + + describe "#once" do + let(matcher) { super.once } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called once" do + dbl.test_method + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub isn't called" do + is_expected.to be_a(failed_match) + end + + it "doesn't match when the stub is called twice" do + 2.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + end + + describe "#twice" do + let(matcher) { super.twice } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called twice" do + 2.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub isn't called" do + is_expected.to be_a(failed_match) + end + + it "doesn't match when the stub is called once" do + dbl.test_method + is_expected.to be_a(failed_match) + end + + it "doesn't match when the stub is called thrice" do + 3.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + end + + describe "#exactly" do + let(matcher) { super.exactly(3) } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called the exact amount" do + 3.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub isn't called" do + is_expected.to be_a(failed_match) + end + + it "doesn't match when the stub is called less than the amount" do + 2.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + + it "doesn't match when the stub is called more than the amount" do + 4.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + end + + describe "#at_least" do + let(matcher) { super.at_least(3) } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called the exact amount" do + 3.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub isn't called" do + is_expected.to be_a(failed_match) + end + + it "doesn't match when the stub is called less than the amount" do + 2.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + + it "matches when the stub is called more than the amount" do + 4.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + end + + describe "#at_most" do + let(matcher) { super.at_most(3) } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called the exact amount" do + 3.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + + it "matches when the stub isn't called" do + is_expected.to be_a(successful_match) + end + + it "matches when the stub is called less than the amount" do + 2.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub is called more than the amount" do + 4.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + end + + describe "#at_least_once" do + let(matcher) { super.at_least_once } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called once" do + dbl.test_method + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub isn't called" do + is_expected.to be_a(failed_match) + end + + it "matches when the stub is called more than once" do + 2.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + end + + describe "#at_least_twice" do + let(matcher) { super.at_least_twice } + subject(match_data) { matcher.match(actual) } + + it "doesn't match when the stub is called once" do + dbl.test_method + is_expected.to be_a(failed_match) + end + + it "doesn't match when the stub isn't called" do + is_expected.to be_a(failed_match) + end + + it "matches when the stub is called twice" do + 2.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + + it "matches when the stub is called more than twice" do + 3.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + end + + describe "#at_most_once" do + let(matcher) { super.at_most_once } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called once" do + dbl.test_method + is_expected.to be_a(successful_match) + end + + it "matches when the stub isn't called" do + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub is called more than once" do + 2.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + end + + describe "#at_most_twice" do + let(matcher) { super.at_most_twice } + subject(match_data) { matcher.match(actual) } + + it "matches when the stub is called once" do + dbl.test_method + is_expected.to be_a(successful_match) + end + + it "matches when the stub isn't called" do + is_expected.to be_a(successful_match) + end + + it "matches when the stub is called twice" do + 2.times { dbl.test_method } + is_expected.to be_a(successful_match) + end + + it "doesn't match when the stub is called more than twice" do + 3.times { dbl.test_method } + is_expected.to be_a(failed_match) + end + end end diff --git a/spec/spectator/dsl/mocks/have_received_spec.cr b/spec/spectator/dsl/mocks/have_received_spec.cr index f5f40f1..57fc0dd 100644 --- a/spec/spectator/dsl/mocks/have_received_spec.cr +++ b/spec/spectator/dsl/mocks/have_received_spec.cr @@ -156,4 +156,184 @@ Spectator.describe "Stubbable receiver DSL" do expect(fake).to_not have_received(:foo).with(:baz) end end + + context "count modifiers" do + double(:dbl, foo: 42) + + let(dbl) { double(:dbl) } + + describe "#once" do + it "matches when the stub is called once" do + dbl.foo + expect(dbl).to have_received(:foo).once + end + + it "doesn't match when the stub isn't called" do + expect(dbl).to_not have_received(:foo).once + end + + it "doesn't match when the stub is called twice" do + 2.times { dbl.foo } + expect(dbl).to_not have_received(:foo).once + end + end + + describe "#twice" do + it "matches when the stub is called twice" do + 2.times { dbl.foo } + expect(dbl).to have_received(:foo).twice + end + + it "doesn't match when the stub isn't called" do + expect(dbl).to_not have_received(:foo).twice + end + + it "doesn't match when the stub is called once" do + dbl.foo + expect(dbl).to_not have_received(:foo).twice + end + + it "doesn't match when the stub is called thrice" do + 3.times { dbl.foo } + expect(dbl).to_not have_received(:foo).twice + end + end + + describe "#exactly" do + it "matches when the stub is called the exact amount" do + 3.times { dbl.foo } + expect(dbl).to have_received(:foo).exactly(3).times + end + + it "doesn't match when the stub isn't called" do + expect(dbl).to_not have_received(:foo).exactly(3).times + end + + it "doesn't match when the stub is called less than the amount" do + 2.times { dbl.foo } + expect(dbl).to_not have_received(:foo).exactly(3).times + end + + it "doesn't match when the stub is called more than the amount" do + 4.times { dbl.foo } + expect(dbl).to_not have_received(:foo).exactly(3).times + end + end + + describe "#at_least" do + it "matches when the stub is called the exact amount" do + 3.times { dbl.foo } + expect(dbl).to have_received(:foo).at_least(3).times + end + + it "doesn't match when the stub isn't called" do + expect(dbl).to_not have_received(:foo).at_least(3).times + end + + it "doesn't match when the stub is called less than the amount" do + 2.times { dbl.foo } + expect(dbl).to_not have_received(:foo).at_least(3).times + end + + it "matches when the stub is called more than the amount" do + 4.times { dbl.foo } + expect(dbl).to have_received(:foo).at_least(3).times + end + end + + describe "#at_most" do + it "matches when the stub is called the exact amount" do + 3.times { dbl.foo } + expect(dbl).to have_received(:foo).at_most(3).times + end + + it "matches when the stub isn't called" do + expect(dbl).to have_received(:foo).at_most(3).times + end + + it "matches when the stub is called less than the amount" do + 2.times { dbl.foo } + expect(dbl).to have_received(:foo).at_most(3).times + end + + it "doesn't match when the stub is called more than the amount" do + 4.times { dbl.foo } + expect(dbl).to_not have_received(:foo).at_most(3).times + end + end + + describe "#at_least_once" do + it "matches when the stub is called once" do + dbl.foo + expect(dbl).to have_received(:foo).at_least_once + end + + it "doesn't match when the stub isn't called" do + expect(dbl).to_not have_received(:foo).at_least_once + end + + it "matches when the stub is called more than once" do + 2.times { dbl.foo } + expect(dbl).to have_received(:foo).at_least_once + end + end + + describe "#at_least_twice" do + it "doesn't match when the stub is called once" do + dbl.foo + expect(dbl).to_not have_received(:foo).at_least_twice + end + + it "doesn't match when the stub isn't called" do + expect(dbl).to_not have_received(:foo).at_least_twice + end + + it "matches when the stub is called twice" do + 2.times { dbl.foo } + expect(dbl).to have_received(:foo).at_least_twice + end + + it "matches when the stub is called more than twice" do + 3.times { dbl.foo } + expect(dbl).to have_received(:foo).at_least_twice + end + end + + describe "#at_most_once" do + it "matches when the stub is called once" do + dbl.foo + expect(dbl).to have_received(:foo).at_most_once + end + + it "matches when the stub isn't called" do + expect(dbl).to have_received(:foo).at_most_once + end + + it "doesn't match when the stub is called more than once" do + 2.times { dbl.foo } + expect(dbl).to_not have_received(:foo).at_most_once + end + end + + describe "#at_most_twice" do + it "matches when the stub is called once" do + dbl.foo + expect(dbl).to have_received(:foo).at_most_twice + end + + it "matches when the stub isn't called" do + expect(dbl).to have_received(:foo).at_most_twice + end + + it "matches when the stub is called twice" do + 2.times { dbl.foo } + expect(dbl).to have_received(:foo).at_most_twice + end + + it "doesn't match when the stub is called more than twice" do + 3.times { dbl.foo } + expect(dbl).to_not have_received(:foo).at_most_twice + end + end + end end diff --git a/src/spectator/matchers/receive_matcher.cr b/src/spectator/matchers/receive_matcher.cr index 79e353c..d261db1 100644 --- a/src/spectator/matchers/receive_matcher.cr +++ b/src/spectator/matchers/receive_matcher.cr @@ -25,6 +25,60 @@ module Spectator::Matchers self.class.new(stub, @count) end + # Returns a new matcher that checks that the stub was invoked once. + def once : self + self.class.new(@stub, Count.new(1, 1)) + end + + # Returns a new matcher that checks that the stub was invoked twice. + def twice : self + self.class.new(@stub, Count.new(2, 2)) + end + + # Returns a new matcher that checks that the stub was invoked an exact number of times. + def exactly(count : Int) : self + self.class.new(@stub, Count.new(count, count)) + end + + # Returns a new matcher that checks that the stub was invoked at least a set amount of times. + def at_least(count : Int) : self + self.class.new(@stub, Count.new(count, nil)) + end + + # Returns a new matcher that checks that the stub was invoked at most a set amount of times. + def at_most(count : Int) : self + self.class.new(@stub, Count.new(nil, count)) + end + + # Returns a new matcher that checks that the stub was invoked at least once. + def at_least_once : self + self.class.new(@stub, Count.new(1, nil)) + end + + # Returns a new matcher that checks that the stub was invoked at least twice. + def at_least_twice : self + self.class.new(@stub, Count.new(2, nil)) + end + + # Returns a new matcher that checks that the stub was invoked at most once. + def at_most_once : self + self.class.new(@stub, Count.new(nil, 1)) + end + + # Returns a new matcher that checks that the stub was invoked at most twice. + def at_most_twice : self + self.class.new(@stub, Count.new(nil, 2)) + end + + # Returns self - used for fluent interface. + # + # ``` + # expect(dbl).to have_received(:foo).exactly(5).times + # ``` + def times : self + self + end + # Short text about the matcher's purpose. def description : String "received #{@stub} #{humanize_count}"