One of the things that I struggled most with when starting to code using MonoTouch was unit testing and mocking.  There was little in the way of information as to how this should be done, and there was even less help in the software patterns encoded into the platform as default by our Apple overlords.  Although the MVC pattern is fine as far as it goes, when you are talking such a mixed bag of technology that is MonoTouch/iPhone development, it comes with its problems.

MVC, Runtime Version and Assembly References

Before talking about unit testing, there are a few things that I should cover that will probably be old news to most of you, but need to be taken into account.

Firstly, assembly references and runtime versions.  When you add a new project targeting iPhone, MonoDevelop creates a particular type of project (specified by a unique ProjectTypeGUID) and sets an immutable Runtime Version of “Mono for iPhone”.  By default, it also adds a set of references to the project, and although these look innocuous enough (see below) there is some magic here.

 

These references are to a distinct (and distinctly different) set of assemblies than the normal, correspondingly named assemblies you would expect if you were building a standard Mono project.  This is due of course to being built to target a different platform.  For the version of MonoTouch that I am currently running (3.2.4) the files that these references correspond to are:

/Developer/MonoTouch/usr/lib/mono/2.1/System.dll
/Developer/MonoTouch/usr/lib/mono/2.1/monotouch.dll
/Developer/MonoTouch/usr/lib/mono/2.1/System.Core.dll
/Developer/MonoTouch/usr/lib/mono/2.1/mscorlib.dll

Now, the primary issue that most people have with testing under MonoTouch (well, at least that I have seen), is that by default they reach for the “Add…Add New Project…NUnit Library Project”.  This results in a project being created with a set of predefined references, and a very different runtime version (generally set to “Mono / .NET 3.5″).  There is nothing stopping you adding unit tests to this project, and referencing classes in your MonoTouch libraries.  Some of these tests will even work.  However, some may not.  The issue comes down to the references, and design patterns (more on that later).

When you add “Package” references to your non-MonoTouch assembly, you are adding Mono assemblies.  Mono assemblies != MonoTouch assemblies.  This is detailed in full here.  Also, you do not get the benefit of “copy local” and transitive binary references that you might be used to.  This generally results in an NUnit error such as the one shown below:

    "System.IO.FileNotFoundException : Could not load file or assembly 'System.Xml, Version=2.0.5.0, Culture=neutral, PublicToken=7cec85d7bea7798e' or one of its dependencies."

With a class or method that references MonoTouch classes, if you attempt to test it NUnit will need get a similar failure for monotouch.dll.  Most people at this point add the monotouch.dll file as a reference, either as a file reference down to the MonoTouch project binary output directory, or better yet to the actual file location as stated above (/Developer/MonoTouch/usr/lib/mono/2.1/monotouch.dll).  On running this now, however, you should get an even bigger bang, quite often resulting in MonoDevelop closing.  An example stack trace is shown below:

System.IO.IOException: Write failure ---> System.Net.Sockets.SocketException: The socket has been shut down
at System.Net.Sockets.Socket.Send (System.Byte[] buf, Int32 offset, Int32 size, SocketFlags flags) [0x00000] in :0
at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in :0
--- End of inner exception stack trace ---
at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in :0
at System.IO.BufferedStream.Flush () [0x00000] in :0
at System.IO.BufferedStream.Dispose (Boolean disposing) [0x00000] in :0
at System.IO.Stream.Close () [0x00000] in :0
at Mono.Remoting.Channels.Unix.ClientConnection.ProcessMessages () [0x00000] in :0
at System.Threading.Thread.StartUnsafe () [0x00000] in :0

If you run MonoDevelop from the Terminal (/Applications/MonoDevelop.app/Contents/MacOS/monodevelop) you will also get a slightly more useful stacktrace:

node `classlib-cocoa' is not defined on the documentation map

Stacktrace:

at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging.void_objc_msgSend_intptr_intptr_bool (intptr,intptr,intptr,intptr,bool) <0x00003>
at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging.void_objc_msgSend_intptr_intptr_bool (intptr,intptr,intptr,intptr,bool) <0x00003>
at MonoTouch.Foundation.NSObject.Dispose (bool) [0x00050] in /Users/plasma/Source/iphone/monotouch/Foundation/NSObject.cs:103
at MonoTouch.Foundation.NSObject.Finalize () [0x00000] in /Users/plasma/Source/iphone/monotouch/Foundation/NSObject.cs:65
at (wrapper runtime-invoke) object.runtime_invoke_virtual_void__this__ (object,intptr,intptr,intptr) <IL 0x0001b, 0x0003d>

This is quite simply due to monotouch.dll being compiled for a different ABI.  Basically, this will just not work.

Fixing References

The easy way to get tests running is to manually reference the exact same binaries that your MonoTouch assembly does.  Simply go to your NUnit project, and add “.Net Assembly” references, and ensure that you references the paths shown above.  One thing to be aware of is generally MonoDevelop will store the assembly reference paths as relative to the project directory, so you will almost certainly need to go in to the project file and ensure that you replace these relative paths with absolute paths.

Patterns That Help

The standard MVC pattern that Apple prefer we use is slightly problematic given the issues around unit testing anything hooking the monotouch.dll asssembly.  If you follow the standard pattern, this means that almost all your Controllers will be untestable, and it is difficult to abstract too much out of these without fundamentally shifting the model you are using.

One solution to this that we are using is the Supervisor Controller pattern.  The implementation we have followed with this pattern is to hollow out the standard UI Controller objects to the absolute minimum that is needed to actual drive the display of the UI, and abstract the actual logic and interaction with the rest of the application to a Supervising Controller.  These Supervising Controllers can be concrete classes, taking an interfaced Controller object as a constructor argument.  This allows the injection of a dummy UI Controller when we want to test them.  As an example:

public interface IMainViewController
{
  void ShowFlipsideView();
}

public class MainViewController : IMainViewController
{
  MainViewSupervisor supervisor;
  IModel model;

  public MainViewController(IModel model)
  {
    this.model = model;
    supervisor = new MainViewSupervisor(this, model);
  }

  #region IMainViewController implementation
  public void ShowFlipsideView()
  {
    throw new NotImplementedException();
  }
  #endregion
}

public class MainViewSupervisor
{
  IModel model;
  IMainViewController controller;
  
  public IMainViewController(IMainViewController controller, IModel model)
  {
    this.controller = controller;
    this.model = model;
  }
}

Testing can then instantiate a Supervisor like this:

[Test()]
public void SupervisorTest()
{
  IMainViewController controller = new MockViewController();
  IModel model = new MockModel();
  MainViewSupervisor supervisor = new MainViewSupervisor(controller, model);

  //Assert.SomethingMeaningful();
}

So far this has worked really well, although there is some additional passing around of objects (for example, the Model object) it does provide a much larger body of testable code.  Hopefully this helps, and if you have a better pattern or method please let me know!