Testing RACCommand in MVVM architecture

So other day I wrote a demo for MVVM with ReactiveCocoa. Creating MVVM project inherently implies writing unit tests, so I was writing few. While doing so I ran into small problem trying to execute RACCommand manually. To give you an idea, look at the following code. I am attaching a RACCommand to the UIButton object as follows.


self.navigationItem.rightBarButtonItem?.rac_command = self.viewModel.addItemButtonActionCommand

where rightBarButtonItem is a button associated with view's navigation bar and addItemButtonActionCommand is a RACCommand associated with viewModel viewModel for a given view.

Now, when user pressed the button, I have a flag, addItemButtonIndicator which gets set which is eventually observed by the view.


addItemButtonActionCommand = RACCommand(signal: { (signal) -> RACSignal? in
    return RACSignal.return(true)
})    
_ = addItemButtonActionCommand?.executionSignals.flatten(1).subscribeNext({ [weak self] (value) in
    if let value = value as? Bool {
        self?.addItemButtonIndicator = value
    }
})

Now in unit test, I want to simulate this action. But for some reason the control would not reach to the block where it flattens the execution signals and extract the bool value returned by the previous RACSignal. The problem was the signal would never complete and attempt to manually execute RACCommand twice in a row would never succeed.

I had to to go through Reactive Cocoa's documentation to see one of the utility methods they had added in order to facilitate the testing associated with RACCommand action. Every time addItemButton is pressed, an additional item is added to an items array. So test looks like this,


expect(tableDemoViewModel?.items.count).to(equal(0))
_ = try?tableDemoViewModel?.addItemButtonActionCommand?.execute(nil).asynchronouslyWaitUntilCompleted()                
expect(tableDemoViewModel?.items.count).to(equal(1))
_ = try tableDemoViewModel?.addItemButtonActionCommand?.execute(nil).asynchronouslyWaitUntilCompleted()                
expect(tableDemoViewModel?.items.count).to(equal(2))

Since execute is executed asynchronously and program is kept waiting until it is completed, we get the desired result and item is added to an array before moving on with rest of the tests.

As a caveat and to quote the method documentation,

Spins the main run loop for a short while, waiting for the receiver to complete.
Because this method executes the run loop recursively, it should only be used on the main thread, and only from a unit test

Hope this will be useful to you. I spent two days trying to figure out how to do it until I found this hacky solution. Not exactly hacky since it is not used in the production at all and it makes sense to use it in unit tests as long as it does what we want it to do.

Jayesh Kawli

I am a web and mobile developer working at Wayfair in Boston, MA. I come to learn so many things during course of life and I write about things which helped me and feel like they can help others too.

Subscribe to Fresh Beginning

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!