RubyMotion Bacon Specs Cheat Sheet
Clone or download
Latest commit 9642b73 May 15, 2015
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
LICENSE Initial commit Mar 29, 2015
README.md Added webstub May 15, 2015

README.md

RubyMotion Bacon Specs Cheat Sheet

by Jamon Holmgren

RubyMotion ships with a built-in fork of MacBacon, which is itself a fork of Bacon, a small pure-Ruby RSpec clone.

RubyMotion's Bacon is fairly capable but not all that well documented. This is a cheat sheet to help bring to mind various strategies for testing RubyMotion apps and gems.

describe/context blocks

describe and context are literally aliases, but you usually use describe to provide a description for the overall goal of a test, and context to establish various scenarios you're running your tests.

describe "String description" do
  # ...
end

describe UIViewController do
  # ...
end

You can provide before and after blocks in any describe/context block.

describe PM::TableScreen do
  context "with a nav_bar" do
    before do
      @screen = PM::TableScreen.new(nav_bar: true)
    end
    after do
      @screen = nil
    end
    
    it "has a nav_bar" do
      @screen.navigationController.should.be.kind_of(PM::NavigationController)
    end
  end
end

should

The basic assertation method of Bacon.

describe Hash do
  it "is a hash instance" do
    obj = {}
    obj.should == {}
  end
end

question methods

Bacon allows you to test the truthiness of a x? method, such as .kind_of?. Remove the question mark from the method to test it, like x.should.be.kind_of(Hash).

describe Hash do
  it "is a hash instance" do
    obj = {}
    obj.should.be.kind_of(Hash)
  end
end

be, a, an

These are mainly just syntactic sugar so you can write something like this:

describe Hash do
  it "is a hash instance" do
    obj = {}
    obj.should.be.a.kind_of(Hash)
  end
end

not

Tests that the opposite is true.

describe Hash do
  it "is not an array" do
    obj = {}
    obj.should.not.be.kind_of(Array)
  end
end

Exceptions

describe "Viper::SnakeCase" do
  it "has a 'Viper::SnakeCase' module" do
    should.not.raise(NameError) { Viper::SnakeCase }
  end
end

tests MyViewController

You can mount a UIViewController into the simulator with the tests class method. This will look for a controller method (or provide its own if you don't).

Keep in mind these tests are very slow. Use unit tests (even for view controllers) where possible.

describe MyScreen do
  tests MyScreen
  
  def controller
    @controller ||= MyScreen.new
  end
  
  after { @controller = nil }
  
  it "has the right title" do
    view("My Screen").should.be.kind_of(UILabel)
  end
end

If you want to have your screen in a navigation controller, make sure your controller method returns the navigationController.

describe MyScreen do
  tests MyScreen
  
  def screen
    @screen ||= MyScreen.new(nav_bar: true) # ProMotion-style
  end
  
  def controller
    screen.navigationController
  end
  
  after { @screen = nil }
  
  it "has the right title" do
    view("My Screen").should.be.kind_of(UILabel)
  end
end

tap

Taps a button on the screen.

describe MyScreen do
  tests MyScreen
  
  def controller
    @controller ||= MyScreen.new
  end
  
  after { @controller = nil }

  it "has a button" do
    tap("Go Forth And Conquer")
    view("Conquered!").should.be.present
  end
end

Testing HTTP requests

Use wait_till which keeps trying the block until it returns a truthy value, up to the timeout specified (defaulted to 3 seconds).

describe "HTTP call" do
  it "returns a result" do
    @ip = nil
    AFMotion::JSON.get("http://ip.jsontest.com/") do |result|
      @ip = result.object["ip"]
    end
    wait_till 20 { @ip.nil? == false }
    @ip.should == "12.34.56.78"
  end
end

Another way to approach this is to use wait_max and the resume command, which is what I recommend:

describe "HTTP call" do
  it "returns a result" do
    @ip = nil
    AFMotion::JSON.get("http://ip.jsontest.com/") do |result|
      result.should.be.success
      @ip = result.object["ip"]
      resume
    end
    wait_max 20 do
      @ip.should == "12.34.56.78"
    end
  end
end

Useful Gems

motion-juxtapose

Visual regression testing. You get a lot of value with a small test.

gem "motion-juxtapose"
describe SettingsScreen do
  tests SettingsScreen

  it "looks like a SettingsScreen" do
    views(UIView).length.should.be > 0 # Ensure views are loaded first
    it_should_look_like "SettingsScreen", 4 # 4% "fuzz factor"
  end
end

motion-stump

gem "motion-stump"

.stub! will replace a method and return the result you specify. It doesn't care if it's called or not, though.

  screen = MyScreen.new(arg: true)
  screen.stub!(:foos, return: [])
  screen.stub!(:bars, return: [ {}, {} ])

.mock! is the same as .stub!, but will fail the test if it's not called.

You can also pass a block to do more stuff, including assertations:

it "does a Google search" do
  API::Client.mock!(:get) do |url, params|
    url.should == "http://google.com"
    params[:q].should == "Nickelback sucks"
    resume
  end
  wait_max 20 {}
end

motion-facon

A good alternative to motion-stump (above). I haven't used it all that much, but its syntax is very pretty. It's fallen a bit out of date, however.

  describe 'PersonController' do
    extend Facon::SpecHelpers

    before do
      @konata = mock('konata', :id => 1, :name => 'Konata Izumi')
      @kagami = mock('kagami', :id => 2, :name => 'Kagami Hiiragi')
    end

    it "should find all people" do
      Person.should.receive(:find).with(:all).and_return([@konata, @kagami])

      Person.find(:all).should == [@konata, @kagami]
    end
  end

webstub

Easily stub out HTTP responses in RubyMotion specs.

  it "should allow you to navigate to a website" do
    controller = ProMotion::WebScreen.new
    stub_request(:get, "https://www.google.com/").
      to_return(body: %q{Google! <form action="/search">%}, content_type: "text/html")

    controller.mock!(:load_finished) do
      controller.html.should.include('<form action="/search"')
      resume
    end
    controller.open_url(NSURL.URLWithString("https://www.google.com/"))
    wait_max 5 {}
  end

Debugging tests

Sometimes, you'll get very useless output from a failed test. Try changing your spec output style.

# In your Rakefile
ENV["output"] ||= "tap"