Skip to content

Commit

Permalink
Remove expect, add stub.
Browse files Browse the repository at this point in the history
  • Loading branch information
xaviershay committed Jul 26, 2014
1 parent 66b91e2 commit 2f1d188
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 33 deletions.
8 changes: 5 additions & 3 deletions doc/api.rb
Expand Up @@ -186,11 +186,13 @@ def save(message, repository: Repository.new)
verify(repo).store(msg: 'hello')
end

# Methods can be pre-emptively expected using `expect`. This has the
# benefit of allowing a return value to be specified.
# Methods can be stubbed using `stub`. This has the benefit of allowing a
# return value to be specified. You still may choose to `verify` the
# invocation as well.
it 'stores a hash document in the repository' do
expect(repo).store(msg: 'hello') { true }
stub(repo).store(msg: 'hello') { true }
assert save('hello', repository: repo)
verify(repo).store(msg: 'hello')
end

# By default, doubling classes that do no exist is allowed. It is assumed
Expand Down
30 changes: 17 additions & 13 deletions lib/xspec/evaluators.rb
Expand Up @@ -153,14 +153,15 @@ def _double(klass, type)
Double.new(ref)
end

# To set up an expectation on a double, call the expected method an
# arguments on the proxy object returned by `expect`. If a return value
# is desired, it can be supplied as a block, for example:
# `expect(double).some_method(1, 2) { "return value" }`
def expect(obj)
Proxy.new(obj, :_expect)
# To set up a stub on a double, call the expected method and arguments on
# the proxy object returned by `stub`. If a return value is desired, it
# can be supplied as a block, for example: `expect(double).some_method(1,
# 2) { "return value" }`
def stub(obj)
Proxy.new(obj, :_stub)
end

# TODO: document
def verify(obj)
Proxy.new(obj, :_verify)
end
Expand Down Expand Up @@ -188,30 +189,33 @@ def initialize(klass)
end

def method_missing(*actual_args)
i = @expected.find_index {|expected_args, ret|
stub = @expected.find {|expected_args, ret|
expected_args == actual_args
}

if i
@expected.delete_at(i)[1].call
else
@received << actual_args
nil
ret = if stub
stub[1].call
end

@received << actual_args

ret
end

# The two methods needed on this object to set it up and verify it are
# prefixed by `_` to try to ensure they don't clash with any method
# expectations. While not fail-safe, users should only be using
# expectations for a public API, and `_` is traditionally only used
# for private methods (if at all).
def _expect(args, &ret)
def _stub(args, &ret)
@klass.validate_call! args

@expected << [args, ret]
end

def _verify(args)
@klass.validate_call! args

i = @received.index(args)

if i
Expand Down
69 changes: 52 additions & 17 deletions spec/unit/doubles_spec.rb
Expand Up @@ -18,35 +18,36 @@ def self.class_method; end
end

describe 'doubles of unloaded classes' do
it 'allows any method to be expected' do
assert_equal nil, subject.instance_eval {
it 'allows a stub to be used multiple times' do
assert_equal 1, subject.instance_eval {
double = instance_double('Bogus')
expect(double).foo
double.foo
stub(double).foo("a") { 1 }
double.foo("a")
double.foo("a")
}
end

it 'allows any return value to be specified' do
it 'allows any return value to be stubbed' do
assert_equal 1, subject.instance_eval {
double = instance_double('Bogus')
expect(double).foo("a") { 2 }
expect(double).foo("b") { 3 }
stub(double).foo("a") { 2 }
stub(double).foo("b") { 3 }
double.foo("b") - double.foo("a")
}
end

it 'requires matching method name' do
assert_equal nil, subject.instance_eval {
double = instance_double('Bogus')
expect(double).foo("a") { 1 }
stub(double).foo("a") { 1 }
double.bar("a")
}
end

it 'requires exact arguments' do
assert_equal nil, subject.instance_eval {
double = instance_double('Bogus')
expect(double).foo("a") { 1 }
stub(double).foo("a") { 1 }
double.foo("b")
}
end
Expand All @@ -62,6 +63,16 @@ def self.class_method; end
}
end

it 'can verify a method that was stubbed' do
assert subject.instance_eval {
double = instance_double('Bogus')
stub(double).foo { 1 }
double.foo
verify(double).foo
true
}
end

it 'raises if method was not called' do
begin
subject.instance_eval {
Expand Down Expand Up @@ -97,18 +108,30 @@ def self.class_method; end

describe 'instance_double' do
describe 'when doubled class is loaded' do
it 'allows instance methods to be expected' do
it 'allows instance methods to be stubbed' do
assert subject.instance_eval {
double = instance_double('LoadedClass')
expect(double).instance_method { 123 }
stub(double).instance_method { 123 }
}
end

it 'does not allow non-existing methods to be expected' do
it 'does not allow non-existing methods to be stubbed' do
begin
assert subject.instance_eval {
double = instance_double('LoadedClass')
expect(double).bogus_method { 123 }
stub(double).bogus_method { 123 }
}
fail "no error raised"
rescue XSpec::Evaluator::Doubles::DoubleFailure => e
assert_include "LoadedClass#bogus_method", e.message
end
end

it 'does not allow non-existing methods to be verified' do
begin
assert subject.instance_eval {
double = instance_double('LoadedClass')
verify(double).bogus_method { 123 }
}
fail "no error raised"
rescue XSpec::Evaluator::Doubles::DoubleFailure => e
Expand All @@ -120,18 +143,30 @@ def self.class_method; end

describe 'class_double' do
describe 'when doubled class is loaded' do
it 'allows instance methods to be expected' do
it 'allows instance methods to be stubbed' do
assert subject.instance_eval {
double = class_double('LoadedClass')
expect(double).class_method { 123 }
stub(double).class_method { 123 }
}
end

it 'does not allow non-existing methods to be expected' do
it 'does not allow non-existing methods to be stubbed' do
begin
assert subject.instance_eval {
double = class_double('LoadedClass')
stub(double).bogus_method { 123 }
}
fail "no error raised"
rescue XSpec::Evaluator::Doubles::DoubleFailure => e
assert_include "LoadedClass.bogus_method", e.message
end
end

it 'does not allow non-existing methods to be verified' do
begin
assert subject.instance_eval {
double = class_double('LoadedClass')
expect(double).bogus_method { 123 }
verify(double).bogus_method
}
fail "no error raised"
rescue XSpec::Evaluator::Doubles::DoubleFailure => e
Expand Down

0 comments on commit 2f1d188

Please sign in to comment.