Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
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.

LABS
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.

LABS
Introducing RR

I'm pleased to introduce a new Test Double (or mock) framework named RR, which is short for Double Ruby.

Why a Double framework and not a Mock framework?

A mock is a type of test double. Since RR supports mocks, stubs, and proxies, it makes sense to refer to RR as a double framework. The proxy is a new usage pattern that I will introduce later in this article, and in more detail in future articles.

Unfortunately, the terminology over doubles has been contradictory depending on the framework. RR's terminology tries to be as faithful as possible to Gerald Meszaros' definition of test doubles. You can read more about test doubles in XUnit Test Patterns and Martin Fowler's article, Mocks aren't Stubs. Regretfully, this does mean that RR will have slightly different terminology than other double frameworks.

How does RR compare to other Mock frameworks?

Most double frameworks focus mainly on mocks (hence the categorization "mock framework"). RR's focus is on enabling more double test patterns in a terse and readable syntax.

RR also does not have dedicated mock objects. It primarily uses the technique called 'double injection'. Names that other frameworks use are 'stub injection', 'mock object injection', 'partial mocking', or 'stubbing'. The term I'll use for this is a double injection, since one or many doubles are being injected into an object's method.

I'll use trivial Rails examples to highlight the syntactical differences between RR, Mocha, Rspec's mocks, and Flexmock. They may or may not be appropriate situations for mocks. The right situations for mocks is an entirely different discussion.

If there is better way to do any of the examples, please post a comment and I will gladly replace it.

Mocks

Here are the ways to mock the User.find method. The expectation is the User class object will receive a call to #find with the argument '99' once and will return the object represented by the variable user.

RR
mock(User).find('99') { user }

Mocha
User.expects(:find).with('99').returns(user)

spec/mocks
User.should_receive(:find).with('99').and_return(user)

Flexmock
flexstub(User).should_receive(:find).with('99').and_return(user).once

Stubs

Here are the ways to stub the User.find method. When the User class object receives a call to find with the argument '99' it will return user1. When User receives find with any other arg, it returns user2.

RR
stub(User).find('99') { user1 }

stub(User).find { user2 }

Mocha
User.stubs(:find).with(anything).returns(2)

User.stubs(:find).with('99').returns(1)

spec/mocks
users = {

  '99' => user1,

  'default' => user2

}

User.stub!(:find).and_return do |id|

  users[id] || users['default']

end

Flexmock
users = {

  '99' => user1,

  'default' => user2

}

flexstub(User).should_receive(:find).and_return do |id|

  users[id] || users['default']

end

Proxy

A proxy used with a mock or stub causes the real method to be called. Expectations can be placed on the invocation and the return value can be intercepted. The main rationales are test clarity and you can ensure that the methods are being called correctly, even after you refactor your code. I will delve more into proxies and their usage patterns in my next article.

Mock Proxy

The following examples set an expectation that User.find('99') will be called once. The actual user is returned.

RR
mock.proxy(User).find('99')

Mocha

You cannot implement this in Mocha. You can do an approximation in this situation however. This technique is not always the solution you need, though.

user = User.find('99')

User.expects(:find).with('99').returns(user)

spec/mocks
find_method = User.method(:find)

User.should_receive(:find).with('99').and_return(&find_method)

Flexmock
find_method = User.method(:find)

User.should_receive(:find).with('99').and_return(&find_method)

Stub Proxy

The following examples intercept the return value of User.find('99') and stub out valid? to return false.

RR
stub.proxy(User).find('99') do |user|

  stub(user).valid? {false}

  user

end

Mocha

Again, this is an approximation, since you cannot use proxies in Mocha.

user = User.find('99')

user.stubs(:valid?).returns(false)

User.stubs(:find).with('99').returns(user)

spec/mocks
find_method = User.method(:find)

User.stub!(:find).with('99').and_return do |id|

  user = find_method.call(id)

  user.stub!(:valid?).and_return(false)

  user

end

Flexmock
find_method = User.method(:find)

flexstub(User).should_receive(:find).with('99').and_return do |id|

  user = find_method.call(id)

  flexstub(user).should_receive(:valid?).and_return(false)

  user

end

instance_of

instance_of is method sugar than allows you to mock or stub instances of a particular class. The following examples mock instances of User to expect valid? with no arguments to be called once and return false.

RR
mock.instance_of(User).valid? {false}

Mocha
User.any_instance.expects(:valid?).returns(false)

spec/mocks
new_method = User.method(:new)

User.stub!(:new).and_return do |*args|

  user = new_method.call(*args)

  user.should_receive(:valid?).and_return(false)

  user

end

Flexmock
new_method = User.method(:new)

flexstub(User).should_receive(:new).and_return do |*args|

  user = new_method.call(*args)

  flexmock(user).should_receive(:valid?).and_return(false)

  user

end

More to come

This concludes the introduction to RR. RR enables some techniques, like proxying, that will make your tests clearer and less brittle. In the next article I will describe into patterns and techniques that will make mocks a more feasible tool for more situations.