Brian Takita's blog
RR 0.6.2 Released
This release features a sexier Double definition block syntax using instance_eval:
mock(User) do
find(1) {user_1}
find(2) {user_2}
end
RR also maintains its non-instance_evaled Double definition block syntax, if the block has an arity of 1:
mock(User) do |expect|
expect.find(1) {user_1}
expect.find(2) {user_2}
end
The instance_eval syntax is very useful in the context of Double Definition chaining:
mock.proxy(User).find(1) do
mock.proxy! do
blogs do
mock.proxy!.find_by_id(2) do |actual_blog|
actual_blog.name.should == "My ranty blog"
actual_blog
end
end
fans do
mock.proxy!.thank_everybody
end
foes do
mock.proxy!.ask_for_forgiveness
end
end
end
user = User.find(1)
user.blogs.find_by_id(2) # My ranty blog
user.fans.thank_everybody
user.foes.ask_for_forgiveness
Of course the previous example is a complicated case of interaction testing, and a simpler state-based and/or hybrid approach may be better, but it demonstrates how using instance_eval can promote readability.
Here is the non-instance_eval solution:
mock.proxy(User).find(1) do
mock.proxy! do |expect|
expect.blogs do
mock.proxy!.find_by_id(2) do |actual_blog|
actual_blog.name.should == "My ranty blog"
actual_blog
end
end
expect.fans do
mock.proxy!.thank_everybody
end
expect.foes do
mock.proxy!.ask_for_forgiveness
end
end
end
instance_eval Controversy
Ola Bini warned against overusing instance_eval. While instance_eval adds beauty, it can also make things more difficult to extend and debug.
In RR, the DoubleDefinitionCreatorProxy (the object that is instance_evaled when defining Doubles using blocks) uses the blank slate pattern, so arbitrary method names can be passed in to define the Double. The Blank Slate implies that the DoubleDefinitionCreatorProxy will not be extended with methods. James Earl Gray explains the pattern that I used for RR.
spec/test helper methods will not be usable within the instance_evaled blocks because the intent of the DoubleDefinitionCreatorProxy object is incompatible with the instance_eval with delegation pattern. DoubleDefinitionCreatorProxy already uses method_missing to create DoubleDefinitions. If you wish to use spec/test helper methods, you will need to memoize it to a variable and use lexical scoping.
Now, one may want to define a helper method in the test/spec that is returned when the Double is invoked.
describe User do
describe "#fans.thank_everybody" do
it "thanks all of my lovely fans" do
user = User.new
memoized_my_lovely_fans = my_lovely_fans
mock(user) {fans {memoized_my_lovely_fans}}
memoized_my_lovely_fans.each {|fan| mock(fan).thank_you}
user.fans.thank_everybody
end
def my_lovely_fans
[Fan.new, Fan.new]
end
end
end
While I think this will be a good addition to RR, I recognize that adding the instance_eval has the possibility of making RR less usable. I'll pay close attention to see how this pans out and am willing to remove it if there are major issues.
RR 0.6.0 Released
I'm pleased to announce the 0.6.0 version of RR. The changes include:
- Declaring Double subject objects without having to pass it in via the mock!, stub!, dont_allow!, instance_of!, and proxy! methods
- Revised Double chaining API
- satisfy matcher
- hash_including matcher
Declaring Double Subjects (The bang methods)
In previous versions of RR, you always needed to pass in the subject of the double. For example:
subject = Object.new
mock(subject).does_something {:and_returns_me}
subject.does_something # :and_returns_me
Now you can have RR automatically create the subject object for you by using the ! method:
subject = mock!.does_something {:and_returns_me}.subject
subject.does_something # :and_returns_me
Now the bang methods by themselves don't really add a whole lot, but when used in the context of Double chaining, they become a powerful addition.
Double Chaining
Nick Kallen presented the use case for Double chaining and contributed a patch for the 0.5.0 release of RR. It has proved useful and is now more fully incorporated into RR. Now you can pass in your subject or use the subject provided by RR by using the ! method. Here are some examples of Double Chaining:
mock(subject).first(1) {mock(Object.new).second(2) {mock(Object.new).third(3) {4}}}
subject.first(1).second(2).third(3) # 4
mock(subject).first(1) {mock!.second(2) {mock!.third(3) {4}}}
subject.first(1).second(2).third(3) # 4
mock(subject).first(1).mock!.second(2).mock!.third(3) {4}
subject.first(1).second(2).third(3) # 4
Of course you have access to the proxy facilities:
mock.proxy(User).find('1').mock.proxy!.children.mock.proxy!.find_all_by_group_id(10)
User.find('1').children.find_all_by_group_id(10) # Makes verifications pass and returns the actual children
You can also do branched Double chaining:
mock(subject).first do
mock! do |expect|
expect.branch1.mock!.branch11 {11} # or expect.branch1 {mock!.branch11 {11}}
expect.branch2.mock!.branch22 {22} # or expect.branch2 {mock!.branch22 {22}}
end
end
o = subject.first
o.branch1.branch11 # 11
o.branch2.branch22 # 22
Satisfy Matcher
Matthew O'Conner submitted a patch that added the satisfy matcher. This adds the ability to add arbitrary argument expectation matchers.
mock(object).foobar(satisfy {|arg| arg.length == 2})
object.foobar("xy")
Hash Including Matcher
Matthew O'Conner also submitted a patch that added the hash_including matcher. This adds a convenient way to assert that the passed-in hash includes certain key/value pairs.
mock(object).foobar(hash_including(:red => "#FF0000", :blue => "#0000FF"))
object.foobar({:red => "#FF0000", :blue => "#0000FF", :green => "#00FF00"})
Mailing list
RR has a mailing lists at:
Also, RR's rubyforge page is at http://rubyforge.org/projects/double-ruby and of course the github page is at http://github.com/btakita/rr.
Yes, and there is more to come
There are many interesting ideas floating around. Joseph Wilk has been playing around with adding Spies into RR. I'm also thinking about adding Double validation scoping into RR. Also, I'm impressed by Mocha's warning of unused stubs. Josh Susser also proposed having a mode where a warning would occur if a mocked method is not implemented on the subject being mocked.
If you have any feature requests, please send an email to the mailing list or add it to the rubyforge tracker.







