├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jamon Holmgren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RubyMotion Bacon Specs Cheat Sheet 2 | 3 | by Jamon Holmgren 4 | 5 | RubyMotion ships with a built-in fork of [MacBacon](https://github.com/alloy/MacBacon), which is itself a fork of [Bacon](https://github.com/chneukirchen/bacon), a small pure-Ruby RSpec clone. 6 | 7 | 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. 8 | 9 | ## describe/context blocks 10 | 11 | `describe` and `context` are [literally aliases](https://github.com/HipByte/RubyMotion/blob/master/lib/motion/spec.rb#L649), 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. 12 | 13 | ```ruby 14 | describe "String description" do 15 | # ... 16 | end 17 | 18 | describe UIViewController do 19 | # ... 20 | end 21 | ``` 22 | 23 | You can provide `before` and `after` blocks in any describe/context block. 24 | 25 | ```ruby 26 | describe PM::TableScreen do 27 | context "with a nav_bar" do 28 | before do 29 | @screen = PM::TableScreen.new(nav_bar: true) 30 | end 31 | after do 32 | @screen = nil 33 | end 34 | 35 | it "has a nav_bar" do 36 | @screen.navigationController.should.be.kind_of(PM::NavigationController) 37 | end 38 | end 39 | end 40 | ``` 41 | 42 | ## should 43 | 44 | The basic assertation method of Bacon. 45 | 46 | ```ruby 47 | describe Hash do 48 | it "is a hash instance" do 49 | obj = {} 50 | obj.should == {} 51 | end 52 | end 53 | ``` 54 | 55 | ## question methods 56 | 57 | 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)`. 58 | 59 | ```ruby 60 | describe Hash do 61 | it "is a hash instance" do 62 | obj = {} 63 | obj.should.be.kind_of(Hash) 64 | end 65 | end 66 | ``` 67 | 68 | ## be, a, an 69 | 70 | These are mainly just syntactic sugar so you can write something like this: 71 | 72 | ```ruby 73 | describe Hash do 74 | it "is a hash instance" do 75 | obj = {} 76 | obj.should.be.a.kind_of(Hash) 77 | end 78 | end 79 | ``` 80 | 81 | ## not 82 | 83 | Tests that the opposite is true. 84 | 85 | ```ruby 86 | describe Hash do 87 | it "is not an array" do 88 | obj = {} 89 | obj.should.not.be.kind_of(Array) 90 | end 91 | end 92 | ``` 93 | 94 | ## Exceptions 95 | 96 | ```ruby 97 | describe "Viper::SnakeCase" do 98 | it "has a 'Viper::SnakeCase' module" do 99 | should.not.raise(NameError) { Viper::SnakeCase } 100 | end 101 | end 102 | ``` 103 | 104 | ## tests MyViewController 105 | 106 | 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). 107 | 108 | Keep in mind these tests are *very slow*. Use unit tests (even for view controllers) where possible. 109 | 110 | ```ruby 111 | describe MyScreen do 112 | tests MyScreen 113 | 114 | def controller 115 | @controller ||= MyScreen.new 116 | end 117 | 118 | after { @controller = nil } 119 | 120 | it "has the right title" do 121 | view("My Screen").should.be.kind_of(UILabel) 122 | end 123 | end 124 | ``` 125 | 126 | If you want to have your screen in a navigation controller, make sure your `controller` method returns the navigationController. 127 | 128 | ```ruby 129 | describe MyScreen do 130 | tests MyScreen 131 | 132 | def screen 133 | @screen ||= MyScreen.new(nav_bar: true) # ProMotion-style 134 | end 135 | 136 | def controller 137 | screen.navigationController 138 | end 139 | 140 | after { @screen = nil } 141 | 142 | it "has the right title" do 143 | view("My Screen").should.be.kind_of(UILabel) 144 | end 145 | end 146 | ``` 147 | 148 | ## tap 149 | 150 | Taps a button on the screen. 151 | 152 | ```ruby 153 | describe MyScreen do 154 | tests MyScreen 155 | 156 | def controller 157 | @controller ||= MyScreen.new 158 | end 159 | 160 | after { @controller = nil } 161 | 162 | it "has a button" do 163 | tap("Go Forth And Conquer") 164 | view("Conquered!").should.be.present 165 | end 166 | end 167 | ``` 168 | 169 | ## Testing HTTP requests 170 | 171 | Use `wait_till` which keeps trying the block until it returns a truthy value, up to the timeout specified (defaulted to 3 seconds). 172 | 173 | ```ruby 174 | describe "HTTP call" do 175 | it "returns a result" do 176 | @ip = nil 177 | AFMotion::JSON.get("http://ip.jsontest.com/") do |result| 178 | @ip = result.object["ip"] 179 | end 180 | wait_till 20 { @ip.nil? == false } 181 | @ip.should == "12.34.56.78" 182 | end 183 | end 184 | ``` 185 | 186 | Another way to approach this is to use `wait_max` and the `resume` command, which is what I recommend: 187 | 188 | ```ruby 189 | describe "HTTP call" do 190 | it "returns a result" do 191 | @ip = nil 192 | AFMotion::JSON.get("http://ip.jsontest.com/") do |result| 193 | result.should.be.success 194 | @ip = result.object["ip"] 195 | resume 196 | end 197 | wait_max 20 do 198 | @ip.should == "12.34.56.78" 199 | end 200 | end 201 | end 202 | ``` 203 | 204 | ## Useful Gems 205 | 206 | ### [motion-juxtapose](https://github.com/terriblelabs/motion-juxtapose) 207 | 208 | Visual regression testing. You get a *lot* of value with a small test. 209 | 210 | ```ruby 211 | gem "motion-juxtapose" 212 | ``` 213 | 214 | ```ruby 215 | describe SettingsScreen do 216 | tests SettingsScreen 217 | 218 | it "looks like a SettingsScreen" do 219 | views(UIView).length.should.be > 0 # Ensure views are loaded first 220 | it_should_look_like "SettingsScreen", 4 # 4% "fuzz factor" 221 | end 222 | end 223 | ``` 224 | 225 | ### [motion-stump](https://github.com/siuying/motion-stump/) 226 | 227 | ```ruby 228 | gem "motion-stump" 229 | ``` 230 | 231 | `.stub!` will replace a method and return the result you specify. It doesn't care if it's called or not, though. 232 | 233 | ```ruby 234 | screen = MyScreen.new(arg: true) 235 | screen.stub!(:foos, return: []) 236 | screen.stub!(:bars, return: [ {}, {} ]) 237 | ``` 238 | 239 | `.mock!` is the same as `.stub!`, but will fail the test if it's not called. 240 | 241 | You can also pass a block to do more stuff, including assertations: 242 | 243 | ```ruby 244 | it "does a Google search" do 245 | API::Client.mock!(:get) do |url, params| 246 | url.should == "http://google.com" 247 | params[:q].should == "Nickelback sucks" 248 | resume 249 | end 250 | wait_max 20 {} 251 | end 252 | ``` 253 | 254 | ### [motion-facon](https://github.com/svyatogor/motion-facon) 255 | 256 | 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. 257 | 258 | ```ruby 259 | describe 'PersonController' do 260 | extend Facon::SpecHelpers 261 | 262 | before do 263 | @konata = mock('konata', :id => 1, :name => 'Konata Izumi') 264 | @kagami = mock('kagami', :id => 2, :name => 'Kagami Hiiragi') 265 | end 266 | 267 | it "should find all people" do 268 | Person.should.receive(:find).with(:all).and_return([@konata, @kagami]) 269 | 270 | Person.find(:all).should == [@konata, @kagami] 271 | end 272 | end 273 | ``` 274 | 275 | ### [webstub](https://github.com/nathankot/webstub) 276 | 277 | Easily stub out HTTP responses in RubyMotion specs. 278 | 279 | ```ruby 280 | it "should allow you to navigate to a website" do 281 | controller = ProMotion::WebScreen.new 282 | stub_request(:get, "https://www.google.com/"). 283 | to_return(body: %q{Google!