We're hiring!

We're actively seeking developers and designers for our Ann Arbor & Detroit locations.

Mocking in RubyMotion: OCMock, motion-stump

We have been using OCMock to assist our unit testing in a RubyMotion application for a few months now. It was on our radar from the beginning because we’ve used it in past iOS development projects, and it has served us well. It’s a well-known library, and we know it can do the job, but it does leave some room for improvement. Specifically, it feels unnecessarily verbose against the the relatively concise backdrop of MacBacon and the Ruby language.

Wouldn’t it be nice if there was a more Ruby-like mocking library? Good news — thanks to Francis Chong’s work on motion-stump, there is!


Born from Stump

Maybe you’ve heard of stump already. It’s a dead-simple mocking library for Ruby written by Jeremy McAnally. motion-stump is a fork of stump made to work with RubyMotion. Thank you, Jeremy and Francis, for sharing the fruits of your labor on these tools with us.

Simplicity – Use the tools you already have.

Think of motion-stump as a minimum viable mocking library. It lets you mock or stub a method on nearly any object you’ve got or create pure mock/stub objects. There are very few options — name the method to mock/stub and provide a return value or block. Using mock! will cause your test to fail if the method isn’t called, stub! won’t.

Any assertions regarding method parameters or complex return scenarios are up to you to handle within the block you provide. I enjoy tools like this because the small surface area meams I’m not going back to a reference very often. It’s especially appropriate in this case because we already have tools to make assertions about values in our tests.

So what does it look like?

This is a simple example taken from the docs; you can read more there if you’re curious.

# Pure stub objects

my_stub = stub(:thing, :return => "dude, a thing!")
my_stub.thing   # => "dude, a thing!"

# Mocking right on the object

my_object = "things are fun"
my_object.mock!(:fancy, :return => "ooo fancy!")
my_object.fancy # => "ooo fancy!"
my_object.mock!(:tancy, :return => "ooo fancy!")

# if my_object.tancy is not called, it will fail the spec

In a more complete context, many of our tests follow a pattern similar to the following. This example is still very simple, but the basic pattern is the same for more complex examples — just more content in the block.

before do
  @target = ObjectUnderTest.new
end
it 'posts a notification named "Jump" with the height' do
  @target.notifCenter = mock('postNotificationName:object:userInfo:') do
    | name, object, userInfo |
    name.should == 'Jump'
    object.should == @target
    userInfo.should == { 'height' => '10m' }
  end
  
  @target.methodUnderTest('10m')
end

Notes

There are a few things I stumbled on for a few minutes but didn’t see covered in the documentation.

  • It uses instance_eval to run your block, so instance variables within your tests don’t work as you might expect within your mock blocks
  • Remember to use method names that include the parameter names, e.g., ‘postNotificationName:object:userInfo:’
  • If mocking a method that takes parameters, you must provide a block that takes the same number of parameters, e.g., @notificationCenter = mock(‘postNotificationName:object:userInfo:’) { |name, obj, userInfo| }
  • Mocking class methods leads to potential test interaction problems. Do so at your own risk. (I should have known better than to try it in the first place, but there was this one scenario we wanted to test…)
     
Jason Porritt (39 Posts)

As a software developer at Atomic Object, Jason has used .NET, RubyMotion, JavaScript, and everything in between to build a wide variety of web a mobile applications.

This entry was posted in Ruby Motion and tagged , , , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.