The Model-View-ViewModel pattern (MVVM) is mostly known from the WPF (Windows Presentation Foundation) world. Thanks to the great thing called Reactive Cocoa, this useful pattern is also applicable for Cocoa applications. The pattern basically separates state and behaviour of a user interface from it’s presentation and moves it to a ViewModel which, unlike a ViewController, is testable, reusable and (mostly) platform-agnostic. This article focuses on writing tests for ViewModels. This is my very first approach – I am admittedly not an expert in ViewModels or Reactive Cocoa, nor am I “the-test-guy”, so I am open to any criticism, or suggestions for to improve the methods used in this article.

The Model-View-ViewModel pattern

First, let’s briefly describe the pattern. The Model-View-ViewModel (MVVM) pattern is basically the same as the Presentation Model (PM). The MVVM was first described by John Gossman in 2005, the PM by Martin Fowler in 2004. However, the ViewModel pattern can be seen as a specialization of the Presentation Model, as it was introduced for technologies where UIs are developed in a more declarative form like XAML, MXML or HTML. The name ViewModel seems to be used more in the Cocoa community, so we keep that name. If you know neither of them, there are many great reads out there already, including the one from Martin Fowler above, but a must read for this article is the short introduction at the ReactiveViewModel project on GitHub.

Back? Well… let’s recap some of the things mentioned there:

MVVM has three Layers:

  • View: This layer is implemented in our ViewControllers. We want to have the view as dumb as possible. This means any logic such as state or behaviour should be moved outside of the ViewControllers into the ViewModel. The View’s responsibilities include: presentation, layout, animation and input handling.
  • ViewModel: The ViewModel encapsulates state and behaviour of the View. Let me give you an example: Imagine that you’re writing a things-to-do app (what else ;-)): The ViewModel now contains an array of to-do items in a form that is easily consumable by the View. This means that any sorting or filtering of the array is already done by the ViewModel. Also, the ViewModel provides methods for adding, deleting, checking and unchecking to-do items, or methods that change the sort order or filtering constraints of the array.
  • Model: The Model, as in classic MVC, refers either to a domain model or the data access layer.

I like this pattern very much because it’s a very clean separation of concerns, results in loose coupling, makes our ViewModel logic reusable, but also because it makes automated testing so much easier.

A testable MVVM App

When the MVVM pattern is applied properly, the view will remain very dumb and we don’t need to test it anymore (imho). The ViewModel still needs to be tested and this can basically be done with the same methods that we test everything else. However, since Reactive Cocoa is a different style of programming and achieves much of it’s magic through signals, we have to adapt our way of testing a bit. There’s not much written about testing ViewModels out there yet, so this article focusses on the practices we developed at YMC so far.

Preparation

In most real world scenarios, a ViewModel will communicate with the outside world: For example, it could use reachability tests to monitor whether the device is online, sends network requests to web services and so on. For our tests we have to ensure that we are able to focus on the ViewModel itself and replace parts involving the outside world using mocking.

Our approach to this is to encapsulate such logic into services as part of the model layer. We also use a dependency injection framework to ensure that we do not instantiate any services directly. This makes testing generally more easy as we can just feed mocked services to our ViewModel for the unit tests. We use Objection as the dependency injection framework.

Like it’s advised in Functional Reactive Programming, you should not include any side effects in the init method of a ViewModel, because this makes testing really hard.

Therefore, we simply follow the pattern to always initialize our ViewModel within awakeFromObjection instead of init and we’re set!

For the testing itself, we use Kiwi. Kiwi is a Behaviour Driven Development Library for iOS development that already includes matchers and is also capable of mocking.

I have set-up a demo project at GitHub, which I’ll use for reference.

EXAMPLE:

 

It should be mentioned that there is also a Kiwi addition called MSSpec, which allows you to set-up tests with mocks for objection, however I find it easier to not use dependency injection for the tests and wire up the dependencies by myself and call the awakeFromObjection initializer when all dependencies are set-up. This gives me slightly more control over what is going on.

2. Signals

Signals can be used to tell our View Controller to do things like refreshing it’s content or to navigate to a different section of the app. Testing signals is a bit more tedious, as they are asynchronous. To test signals we subscribe to the tested signal, store it’s result in a variable, execute the conditions for the signal to happen and then check the correctness of the variable using Kiwi’s expectFutureValue.

Example:

 

3. Commands

Commands represent the execution of some action. They can be bound to UI controls like buttons, but may also be executed manually. They include an enabled signal which automatically enables or disables the corresponding UI control. Here is a very good post explaining commands.

Commands are the most complex Reactive Cocoa constructs to test: We have to test the conditions under which it is enabled or not, but also test the asynchronous execution which result in side-effects or service invocations we have to validate. But commands are such  wonderful ways to represent any action in our ViewModel, that I would suggest you use them.

Example:

 

Conclusion

Reactive Cocoa is a gorgeous framework. Gone are questions like “which should I use? Notification or Delegate?” With Reactive Cocoa we just use signals! But it’s more like replacing notifications or delegates – it’s a whole different paradigm and when we want to create tests for that, we have to work around some hurdles. I really don’t like the presented way to test signals and commands much. It doesn’t feel 100% right. Being just our very first approach, I guess there are smarter ways of doing these asynchronous tests. And if not, we should maybe consider adapting our testing-frameworks to functional reactive programming. Get in touch and let me know about your experiences with testing reactive ViewModels.

References

0 Kommentare

Einen Kommentar abschicken

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *