RSpec.describe Contract do describe ".override_validator" do around do |example| Contract.reset_validators example.run Contract.reset_validators end it "allows to override simple validators" do Contract.override_validator(Hash) do |contract| lambda do |arg| return false unless arg.is_a?(Hash) # Any hash in my system should have :it_is_a_hash key! return false unless arg.key?(:it_is_a_hash) contract.keys.all? do |k| Contract.valid?(arg[k], contract[k]) end end end klass = Class.new do include Contracts::Core Contract ({ :a => Contracts::Num, :b => String }) => nil def something(opts) nil end end obj = klass.new expect do obj.something(:a => 35, :b => "hello") end.to raise_error(ContractError) expect do obj.something( :a => 35, :b => "hello", :it_is_a_hash => true ) end.not_to raise_error end it "allows to override valid contract" do Contract.override_validator(:valid) do |contract| if contract.respond_to?(:in_valid_state?) lambda do |arg| contract.in_valid_state? && contract.valid?(arg) end else lambda { |arg| contract.valid?(arg) } end end stateful_contract = Class.new(Contracts::CallableClass) do def initialize(contract) @contract = contract @state = 0 end def in_valid_state? @state < 3 end def valid?(arg) @state += 1 Contract.valid?(arg, @contract) end end klass = Class.new do include Contracts::Core Contract stateful_contract[Contracts::Num] => Contracts::Num def only_three_times(x) x * x end end obj = klass.new expect(obj.only_three_times(3)).to eq(9) expect(obj.only_three_times(3)).to eq(9) expect(obj.only_three_times(3)).to eq(9) expect do obj.only_three_times(3) end.to raise_error(ContractError) expect do obj.only_three_times(3) end.to raise_error(ContractError) end it "allows to override class validator" do # Make contracts accept all rspec doubles Contract.override_validator(:class) do |contract| lambda do |arg| arg.is_a?(RSpec::Mocks::Double) || arg.is_a?(contract) end end klass = Class.new do include Contracts::Core Contract String => String def greet(name) "hello, #{name}" end end obj = klass.new expect(obj.greet("world")).to eq("hello, world") expect do obj.greet(4) end.to raise_error(ContractError) expect(obj.greet(double("name"))).to match( /hello, #\[.*Double.*"name"\]/ ) end it "allows to override default validator" do spy = double("spy") Contract.override_validator(:default) do |contract| lambda do |arg| spy.log("#{arg} == #{contract}") arg == contract end end klass = Class.new do include Contracts::Core Contract 1, Contracts::Num => Contracts::Num def gcd(_, b) b end Contract Contracts::Num, Contracts::Num => Contracts::Num def gcd(a, b) gcd(b % a, a) end end obj = klass.new expect(spy).to receive(:log).with("8 == 1").ordered.once expect(spy).to receive(:log).with("5 == 1").ordered.once expect(spy).to receive(:log).with("3 == 1").ordered.once expect(spy).to receive(:log).with("2 == 1").ordered.once expect(spy).to receive(:log).with("1 == 1").ordered.once obj.gcd(8, 5) end end end