Auto-mock iOS APIs


#1

Advice please! I’m trying to make a go of TDD in my iOS app. Works fine for all the POROs and I can mock/stub all the network access but, of course, big chunks of my code are in controllers which interact directly with iOS. I have this idea to mock/stub the iOS classes automatically.

For example:

class PlayerController < UIViewController
  def viewDidLoad
      # blah blah
  end
end

I want to write a test like this (using an imaginary API built on top of minitest):

class PlayerControllerTest < IOSTest
  setup do
    @player = MPMusicPlayerController.new
    @controller = PlayerController.new
  end

  test 'should show the current track when the view loads' do
     @player.nowPlayingItem = ['Wish You Were Here', 'Pink Floyd', 1234567]

     @controller.viewDidLoad

     assert_equal 'Wish You Were Here', @controller.now_playing_track.text
     assert_equal 'Pink Floyd', @controller.now_playing_artist.text
  end   
end

In the old days, I would have tried to either (1) isolate my code from iOS or (2) mock all the iOS methods that my code interacts with. (1) is not practical because the isolation layer would have a huge surface area and (2) is not practical because of all the little iOS constants that my code has to rely on.

I understand that I can do a lot of this now with Bacon and testing via the simulator but I want to write unit tests TDD-style. I don’t want to interact with any part of iOS in my tests.

Here’s my idea:

EITHER: I can use code-generation to build a bridging layer as a one-off rake task (similar to the way that RubyMotion does). I could maybe even leverage RubyMotion’s bridging layer somehow. (STATIC OPTION)

OR: I could monkey-patch method_missing and const_missing to do the right thing at runtime where “right thing” means making a guess at the right thing to return, calling a factory method or using expectations defined in the test. (DYNAMIC OPTION)

Either way, I’d hand-code smarter fakes of common iOS objects like UIViews and UIViewControllers (similar to the way that Rails has smart fakes of request and response objects) that know about things like view lifecycles and sending notifications.

For folks with an understanding of how RubyMotion works, does either one of these approaches sound obviously better to you? Any better ideas?


#2

Link to conversations in Slack: https://motioneers.slack.com/archives/C055RDLS0/p1527628579000358