RSpec 4 iPhone BDD?


Is anybody doing this yet? I made an effort to use RSpec for iPhone development almost a year ago but got stumped on Ruby mock objects that didn’t seem to to play nice with CocoaTouch objects. I got a lot closer than my blog post would lead you to believe but stopped due to deadlines, and my lack of experience with both Ruby and ObjC. Tonight I dug up the old project and tried to remember where I left off…

…Aahh it’s al coming back to me now! I had wrapped the MockObject support in Ruby to ensure it received parameters from CocoaTouch. For some silly reason parameters passed directly from CocoaTouch objects are dropped/ignored by whatever mock framework Ruby is using with RSpec. Forgive me for not knowing more about the internals but I don’t do Ruby and this was from last November. Here’s an example of the RSpec I modified to allow parameters to pass cleanly. It also has the buddings of a dynamic message dispatch thingy I couldn’t get working because it makes no sense to hard code every anticipated method invocation. If one of you Ruby guys could jump in and patch this up or if somebody could point me to an already viable alternative that’d be so swell…


require File.dirname(__FILE__) + '/test_helper'

require "BowlingController.bundle"
OSX::ns_import :BowlingController
OSX::ns_import :BowlingProtocol
require 'delegate'

include OSX

class ObjCMock
  attr_accessor :delegate
  def roll(num)
    @delegate.roll(num)
  end
  
  # def method_missing(m, *args)  
  #   puts "Forwarding message"
  #   @delegate.send(m, args)
  # end
    
  def initialize(theMock)
    @delegate = theMock
  end
end

describe BowlingController do
  before(:each) do
    @controller = BowlingController.new
    @bowling = mock('BowlingProtocol')
    @bowling.stub!(:roll)
    @controller.bowling = ObjCMock.new(@bowling)
    @text_field = mock('Pins')
    @text_field.stub!(:intValue).and_return(10)
    @controller.pins = @text_field
  end

  it "should roll a ball" do
    @controller.roll
  end

  it "should roll a ball and get the value from the pins outlet" do
    @text_field.should_receive(:intValue).and_return(0)
    @controller.roll
  end

  it "should be an OSX::NSObject" do
    @controller.is_a?(OSX::NSObject).should == true
  end

  it "should have an outlet to a bowling object" do
    @controller.bowling = @bowling
  end

  it "should send the pin value to the bowling object" do
    #This line wraps the mock with the class defined above
    @controller.bowling = ObjCMock.new(@bowling)
    #The following line will cause failure calling directly into the mock
#    @controller.bowling = @bowling
    @bowling.should_receive(:roll).with(10)

    @controller.roll
  end
  
  it "should have an outlet to the score" do
    @score = mock('Score')
    @controller.score = @score
  end

end
//
//  BowlingController.h
//  BowlingController
//
//  Created by FIXME on 2008-11-10.
//  Copyright 2008 FIXME. All rights reserved.
//

#import "BowlingController.h"
#import "BowlingProtocol.h"

@implementation BowlingController
@synthesize pins;
@synthesize bowling;
@synthesize score;

-(void) roll{
	int val = (int) [self.pins intValue];
	printf("Value is: %i", val);
	[self.bowling roll:val];
}

@end

// This initialization function gets called when we import the Ruby module.
// It doesn't need to do anything because the RubyCocoa bridge will do
// all the initialization work.
// The rbiphonetest test framework automatically generates bundles for 
// each objective-c class containing the following line. These
// can be used by your tests.
void Init_BowlingController() { }

Dynamic Dispatch Discrepancy


*Update*
By the way I unsuccessfully tried overriding the methods() method and almost went as far as overriding send on my mock wrapping ObjCMock class. My experience in MOP tells me that overriding send is likely rooted in trouble so I decided against it.

Yesterday’s Ruby drama ended with my finding a hack around the dropped parameters. If I was more familiar with Ruby, had more time to kill, or if it were Groovy instead then my solution would be much cleaner.

The problem:
Ruby Mocks under RSpec (have no idea if the problem persists in other Ruby scripting environments) do not receive parameters correctly from native Cocoa objects.

The hack:
Use a home-grown mock to intercept the parameter and pass along to the Ruby mock.

The code: (bowling_controller_spec.rb)

require File.dirname(__FILE__) + ‘/test_helper’

require “BowlingController.bundle”
OSX::ns_import :BowlingController
OSX::ns_import :BowlingProtocol
require ‘delegate’

include OSX

class ObjCMock
attr_accessor :delegate
def roll(num)
@delegate.roll(num)
end

# def method_missing(m, *args)
# puts “Forwarding message”
# @delegate.send(m, args)
# end

def initialize(theMock)
@delegate = theMock
end
end

describe BowlingController do
before(:each) do
@controller = BowlingController.new
@bowling = mock(‘BowlingProtocol’)
@text_field = mock(‘Pins’)
@text_field.stub!(:intValue).and_return(10)
@controller.pins = @text_field
end

it “should roll a ball” do
@controller.roll
end

it “should roll a ball and get the value from the pins outlet” do
@text_field.should_receive(:intValue).and_return(0)
@controller.roll
end

it “should be an OSX::NSObject” do
@controller.is_a?(OSX::NSObject).should == true
end

it “should have an outlet to a bowling object” do
@controller.bowling = @bowling
end

it “should send the pin value to the bowling object” do
@controller.bowling = ObjCMock.new(@bowling)
# @controller.bowling = @bowling
@bowling.should_receive(:roll).with(NSNumber.numberWithInt(10))

@controller.roll
end
end

I left in the commented out method_missing magic that doesn’t work the way I’d expect it. Ideally I’d like to tuck this wrapping mock to the side without needing to define the specific signature I’m mocking. Ideally method_missing would give me the ability to answer any message. Alas, I get “does not reckognize selector” errors from NSProxy when I enable it. I have a feeling that NSProxy probably looks through the defined methods on it’s delegate and throws this error without 1st trying to invoke the method… effectively killing any dynamic magic ability from objects passed into Cocoa land. I also have a feeling that there are a few other holes in the proxy approach with Ruby/Cocoa. If anybody happens to know the details feel free to chime in.

Ruby mock objects drop parameters


Maybe it’s me but it sure seems like Ruby mocks are dropping the parameters passed in from Objective-C. I’m about to give up on this venture into RSpec for iPhone controller code development. In my mind I see it as a huge possibility and I’m willing to give it a few more shots before I wave the white flag. Here’s what I observed so far. (Keep in mind this is coming from a guy who just learned Objective-C, XCode, Ruby, and RubyCocoa reading Apple docs in the shower this morning!) If I call into a Ruby mock passing an int primitive directly from Objective-C while running under RSpec on the 10th day of the 11th month with 3/4th of my coffee mug occupied with the ol’ black magic juice (sorry but from my mobile experience it’s definitely important to set the stage listing all of your environment variables and making sure the stars are all aligned…) then I get a program crash. If I instead pass an NSNumber object wrapping the int then the parameter is silently ignored. If I call into a simple Ruby object defined inline in the spec then the parameter seems to come through just fine. If I then wrap the Ruby Mock with my own crude inlined mock passing the value from ObjC through my mock and into the Ruby Mock then all is right with the world… abusive husbands across America stop beating their wives while they join hands and sing Koom-by-ya… aggressive drivers let you over in heavy traffic, and the DOW jumps up 200 points. To see what I mean, here’s the updated spec code from my earlier posting:

bowling_controller_spec.rb

require File.dirname(__FILE__) + ‘/test_helper’

require “BowlingController.bundle”
OSX::ns_import :BowlingController
OSX::ns_import :BowlingProtocol

include OSX

class MyMock
attr_accessor :delegate
def roll(num)
puts “Number is #{num}”
puts “Calling delegate #{@delegate}…”
@delegate.roll(num)
puts “Done with delegate…”
end
end

describe BowlingController do
before(:each) do
@controller = BowlingController.new
@bowling = mock(‘BowlingProtocol’)
@text_field = mock(‘Pins’)
@text_field.stub!(:intValue).and_return(10)
@controller.pins = @text_field
end

it “should roll a ball” do
@controller.roll
end

it “should roll a ball and get the value from the pins outlet” do
@text_field.should_receive(:intValue).and_return(0)
@controller.roll
end

it “should be an OSX::NSObject” do
@controller.is_a?(OSX::NSObject).should == true
end

it “should have an outlet to a bowling object” do
@controller.bowling = @bowling
end

it “should send the pin value to the bowling object” do
mock = MyMock.new
puts “Setting delegate to #{@bowling}”
mock.delegate = @bowling
puts “Now passing mock into real code…”
@controller.bowling = mock
# @controller.bowling = @bowling
@bowling.should_receive(:roll).with(NSNumber.numberWithInt(10))

@controller.roll
end
end

I updated the controller to use a protocol:
BowlingController.h

//
// BowlingController.h
// BowlingController
//
// Created by FIXME on 2008-11-10.
// Copyright 2008 FIXME. All rights reserved.
//

#import

@class UITextField;
@protocol BowlingProtocol;

@interface BowlingController : NSObject {
UITextField* pins;
id bowling;
}
@property (nonatomic, retain) UITextField* pins;
@property (nonatomic, retain) id bowling;

-(void) roll;
@end

bowling_controller_spec.rb

//
// BowlingController.h
// BowlingController
//
// Created by FIXME on 2008-11-10.
// Copyright 2008 FIXME. All rights reserved.
//

#import “BowlingController.h”
#import “BowlingProtocol.h”

@implementation BowlingController
@synthesize pins;
@synthesize bowling;

-(void) roll{
int val = [self.pins intValue];
[self.bowling roll:[NSNumber numberWithInt:10]];
}

@end

// This initialization function gets called when we import the Ruby module.
// It doesn’t need to do anything because the RubyCocoa bridge will do
// all the initialization work.
// The rbiphonetest test framework automatically generates bundles for
// each objective-c class containing the following line. These
// can be used by your tests.
void Init_BowlingController() { }

BowlingProtocol.h

@protocol BowlingProtocol
-(void) roll:(NSNumber*)pins;
@end

More on this later as I explore…

RSpec for iPhone development


So last night I did some laundry, taught my oldest daughter how to write code, took both of my girls shoe shopping, washed the little one’s hair, raked the leaves cooked dinner, then finally got a start on trying RSpec against a Cocoa Touch controller class. Yep, I was kinda sorta busy. Had I a little more time I wouldn’t be stumped where I am now but since my schedule is as packed as a VW beetle carrying a family of 6 on a Ski vacation for 2 months there’s little wiggle room for discovery and surprises, so instead I ask you, my humble readers (all three of you) to figure out the hard stuff for me. I will gladly repay you by splattering your hard work across the internets next to a picture of my smiling eyebrows and deep claims referencing my name exclusively.

I’m working through a Bowled over by Ruby/Cocoa example written against Mac OSX Cocoa APIs and trying to adapt it to CocoaTouch. The first challenge comes from trying to use RubyCocoa to access a controller bound to certain CocoaTouch APIs like UIKit. It doesn’t work. So instead I was clever enough to side step the problem and generate my BowlingController as a vanilla NSObject using the “script/generate model” command from Dr. Nic’s RBiPhoneTest. I use rbiPhoneTest to setup the project test structure as well so now I have a rake file in my root that I use to build the necessary BowlingController.bundle for RSpec (or any Ruby code) to access. I will plan a write up on all of this after I get through the entire tutorial. Long story lengthened I have RSpec loading and running my BowlingController, I’ve made several iterations even into creating IBOutlet/IBAction bindings for Interface Builder. (I’m not sure if they work b/c I haven’t gotten that far yet.) I get stuck at the part where my controller is supposed to pass the pins value from the text field into the bowling model object. My Ruby bowling stub reports that roll has been invoked with no arguments even though I am most definitely passing a value into the method. I keep getting, “Spec::Mocks::MockExpectationError in ‘OSX::BowlingController should send the pin value to the bowling object’
Mock ‘Bowling’ expected :roll with (10) but received it with (no args)
./test/bowling_controller_spec.rb:38:”
I’ve even went as far as passing a literal 10 into the call. At this point it looks like some sort of Ruby/Cocoa alignment issue or something.

bowling_controller_spec.rb

require File.dirname(__FILE__) + ‘/test_helper’

require “BowlingController.bundle”
OSX::ns_import :BowlingController

include OSX

describe BowlingController do
before(:each) do
@controller = BowlingController.new
@bowling = mock(‘Bowling’)
@text_field = mock(‘Pins’)
@text_field.stub!(:intValue).and_return(10)
@controller.pins = @text_field
end

it “should roll a ball” do
@controller.roll
end

it “should roll a ball and get the value from the pins outlet” do
@text_field.should_receive(:intValue).and_return(0)
@controller.roll
end

it “should be an OSX::NSObject” do
@controller.is_a?(OSX::NSObject).should == true
end

it “should have an outlet to a bowling object” do
@controller.bowling = @bowling
end

it “should send the pin value to the bowling object” do
@controller.bowling = @bowling
@bowling.should_receive(:roll).with(10)

@controller.roll
end
end

BowlingController.h

#import

@class UITextField;
@class Bowling;

@interface BowlingController : NSObject {
UITextField* pins;
Bowling* bowling;
}
@property (nonatomic, retain) UITextField* pins;
@property (nonatomic, retain) Bowling* bowling;

-(void) roll;
@end

BowlingController.m

#import “BowlingController.h”
#import “Bowling.h”

@implementation BowlingController
@synthesize pins;
@synthesize bowling;

-(void) roll{
[self.bowling roll:[self.pins intValue]];
}

@end

// This initialization function gets called when we import the Ruby module.
// It doesn’t need to do anything because the RubyCocoa bridge will do
// all the initialization work.
// The rbiphonetest test framework automatically generates bundles for
// each objective-c class containing the following line. These
// can be used by your tests.
void Init_BowlingController() { }

Bowling.h

#import

@interface Bowling : NSObject {

}
– (void) roll:(int) pins;
@end

Bowling.m

#import “Bowling.h”

@implementation Bowling
– (void) roll:(int) pins{
}

@end

// This initialization function gets called when we import the Ruby module.
// It doesn’t need to do anything because the RubyCocoa bridge will do
// all the initialization work.
// The rbiphonetest test framework automatically generates bundles for
// each objective-c class containing the following line. These
// can be used by your tests.
void Init_Bowling() { }

Note to self: I gotta find better analogies. These are really starting to suck.

The Backward Guide to Ruby Cocoa


First pretend you’re an Objective C professional and jump into iPhone development. (It helps to have absolutely no experience at this step.) Next, act over zealous about Test Driven Design and find the only unit testing framework that’ll half run against an iPhone project, one that just happens to be implemented in Ruby. Throw in a deadline just for fun You now have a recipe for achieve great feats in all things Ruby/Cocoa related and can now complain to your coworkers how stoopid Ruby is because it can only seem to intermittently find your method that is OBVIOUSLY defined on an objective C class you wrote moments ago.

Dumb Ruby programmers! How can anybody claim this technology is superior? Who cares that you didn’t read the documentation? Didn’t Joel say somewhere that users don’t read docs? Why can’t this stuff be easy enough to just pick up and use out of the box? After all if this were Java or Groovy you’d be finished by now!

Mumble a few more obscenities or discouraging words of condemnation toward the ultimately inferior platforms written by those who label themselves Rubyists or Cocoaists or whatever and then Google the thing you’re trying to do. What’s that towards the top of the page about how Ruby/Cocoa bridges work? Paraphrased:

The average idiot will note that RubyCocoa uses the Libffi library to call the Objective-C methods implementations. That means the first method call will go through the #method_missing mechanism, you moron, and then the Ruby method will be defined on the Ruby proxy class, thus allowing all (initial queries to Obj C classes to not know about the method while) further calls to be direct, and faster.

The best way to drive a developer nutz is to tell him something doesn’t exists when he/she is absolutely certain of the contrary. Go against logic. 1+1!=2, set a = b and (a == b) == false, function fooBar cannot be found because function fooBar is defined. There are plenty of other examples I’m sure.

Yes genius, you are attempting to learn Ruby Cocoa from the ass end. You know this but you are too arrogant to admit it, even to your significant other. Sure you’ll lie in bed and discuss things like how was work. The typical conversation leads you into a rambling session beginning with, “I had a bad day…” with you trying to explain to your incoherent loved one why things are getting you down. You can’t admit failure at this point as your loved one is lying, waiting for your to reveal weakness… any weakness that will serve as ammo in the next argument when you forget to cut the grass or have to run partially clad after the trash truck that powers away mercilessly from your residence because you had too many Bud Ice’s the prior night and passed out with that uncomfortable feeling that you knew you needed perform some important task but what the heck is it? No need to explain that you can’t perform your job because you skipped the fundamental steps required (actually learning the technology) to be productive in any capacity.

No worry, you can learn this stuff after you’ve used it. Learning is for sissies anyway, right? Who takes a class on starting an IV before declaring themselves a qualified Phd? That’s utter nonsense isn’t it? Just jam the damned needle somewhere close to the greenish looking line thing running down the arm and you’re all good! After the lawsuite begin you can enrol in Lincoln Tech and figure out why the deadly infection began in your first and only client. Yes we learn after we’ve trash talked the entire industry because it’s everyone else’s fault by now. The APIs should be written in a way that require no knowledge acquisition!

Disclaimer: The entirety of this article is meant tongue in cheek, while I appreciate all feedback positive and especially negative, don’t waste your time claiming Ruby and/or Cocoa is the best thing since Parish Smith got back with Eric Sermon. As a lover/consumer/advocate of anything programming related you’ll be preaching to the choir or buttering the tub of Land ‘o Lakes… you’ll be mowing the lawn mower… spray painting the brush, redundantly repeating the repetitive, repeatedly, or threading the thread spool. Save your keystrokes and don’t respond.

Ruby Calls ObjC!


I think I got it now! The other day I mentioned a problem with assert_responds_to in Ruby as you call into an ObjC controller. Today I had a different experience calling the “respond_to?” method on Object. Or so I thought the experience was different. Here’s what I observed, using autotest sometimes the assertion would pass. In my other example I was wsing the respond_to? message instead but sending this message after actually calling the method on the controller. Here’s what I mean.

This fails:

  def test_my_model_can_request_my_data
	myModel = MapModel.alloc.init
	assert mapModel.respond_to?("requestMyData"), "MyModel should define a requestMyData method"
	assert_not_nil myModel.requestMyData
  end

This passes:

  def test_my_model_can_request_my_data
	myModel = MapModel.alloc.init
	assert_not_nil myModel.requestMyData
	assert mapModel.respond_to?("requestMyData"), "MyModel should define a requestMyData method"
  end

It’s as if you have to send the message first before Ruby knows if the message is defined. That was sort of the thing I saw the other day. Adding the comment then removing the comment on an assert over a defined method gave different results. I’m going to try some introspection next, I’m just a little excited on figuring things out.

RBIPhoneTest Oddities?


Of course I don’t expect things to work 100% so it’s no surprise that I found an oddity in the RBIPhoneTest project (brought to you by Dr. Nic). No discredit at all toward Dr. Nic, as he did a fantastic job with what little Apple gives you. Maybe it’s my misunderstanding of Ruby testing and the ZenTest autotest thing but I got autotest running since finishing that screencast last night and I noticed that inserting a simple “assert_respond_to” call started showing a test failure when I thought it should pass. I then found something even funnier. After commenting the failing “assert_respond_to” call the test passed! Yes, that’s not really funny but get this. I uncommented the very same line that was failing and got the test to pass!! Now that’s funny! not in the “haha” sense of funny but more in the “things that make you go hmm…” Arseniol Hall kinda funny. It’s repeatable as well. I change the message name in the responds to assertion to make it fail, change it back to make it right but it still fails, comment the line to make it pass, uncomment and it still passes! More on this later…