How to mock singletons for testing in Cappuccino with OJTest

This post has been lurking for a while, but with the release of OJMoq 2, I decided I should probably just put this out there.  If you don't want to read about the motivation, skip to the sixth paragraph.

I've been on a testing kick for the past few months, motivated by the maturity of the OJTest library (thanks largely to the hard work of Derek Hammer).  OJTest has support for stubs, mocks and spies, which turns out to be very helpful when testing Cappuccino applications.  But one of the issues that I quickly ran into was writing tests around classes that make calls to Cappuccino classes that use default singletons.  For example, many parts of the Cappuccino code will use [CPNotificationCenter defaultCenter] for notifications or [CPBundle mainBundle] to bring in resources/read properties from an Info.plist file.  

So say you have a CPDocument subclass, called SCDocument, that you are writing tests for.  In a unit test, you may just instantiate SCDocument manually and exercise methods for it, but let's say you are being a bit more integrative in your tests and you want to instantiate your document the way Cappuccino normally would - by calling [[CPDocumentController sharedDocumentController] newDocument:self].  You'll realize that CPDocumentController looks to [CPBundle mainBundle] to find the document type to instantiate.  But if you don't have an Info.plist in your Tests directory, this fails.  So how can you tell CPDocumentController the class to instantiate?

One common way people work around problems like this is by using dependency injection.  To do this with the above example, you would first change CPDocumentController to accept a bundle and change all the calls to [[CPBundle mainBundle] call] to be [injectedBundle call].  Then, in your test, you would create a stub called stubBundle that returns the proper document type when you call [[stubBundle infoDictionary] objectForType:@"CPBundleDocumentTypes"].  Then you would inject this stub into CPDocumentController by calling [[CPDocumentController sharedController] setInjectedBundle:stubBundle].  

Simple, right? I didn't think so either.  When you add to this the fact that you would need to refactor a significant portion of Cappuccino to make all the classes accept dependencies, and that you would be making the design of Cappuccino's classes be based heavily on your testing needs, which is arguably bad, and that you would probably be making the API diverge farther away from Cocoa, you would end up with a mess quite a bit more messy than this sentence.

So, I decided to this another way.  Turns out in Cappuccino, you can treat class names like any other declared variable.  Since Cappuccino classes are also objects that understand selectors, you can really just stub out method calls to the class itself and simply reassign the global variable referring to the class to point to your stub.  So, in the above example, instead of making a stub CPBundle instance, I do the following (note that this is code written for OJMoq 1 - in the newer version, I would probably need to make a stub of CPBundle and put in fake implementations of a few more methods):

Now, any classes within Cappuccino that refer to CPBundle will get my stub instead without me having to change anything about these classes.  And once my test is done, the tearDown makes sure everything is back to normal.  

Posted
 

Socket To 'Em

If you follow our UserVoice forums, you might know that my latest directive, by popular vote, is to add some kind of collaboration to Mockingbird.  Though I haven't really decided on how this is going to look, I couldn't help but check out the variety of real-time-push-comet-right-here-right-now-web frameworks. Since I knew nothing (and now know next to nothing) about what's out there, the first step was, well, to see what's out there.  I ended up deciding to play around with Node.js + WebSockets (specifically, using Socket.IO - a nifty little framework on top of Node.js that will use WebSockets when possible and fall back to other Comet methods otherwise).

Instead of making the usual sample chat client, I decided to try to make a very simple collaborative drawing application by integrating Socket.IO with Cappuccino so that I can also try to play around with various ways to synchronize user data (see my Hacker News thread about it).  I haven't actually put in any kind of sophisticated data synchronization yet, but the code for what I do have so far is here: http://github.com/saikat/drawtogether.

And here it is in action:

Check out the README at the Github repo to get it up and running.  The code itself is fairly simple.  All the backend work is in server.js, and in there, the actual interesting parts that are doing anything other than serving static files start with the line "var listener = io.listen...".  On the client-side, I made a very simple Objective-J class that wraps the Socket.IO client called SCSocket (located in client/SCSocket.j).  It's up to you to simply set a delegate on this class and implement any of four methods (see setDelegate: on SCSocket) to handle notifications from the backend.  To send notifications to the Node server, simple call [[SCSocket sharedSocket] sendMessage:].

Posted
 

The Canvas Caveat

This isn't going to be a real post, but in case anyone else is having issues with performance on canvas-backed views, I learned something fairly important this past week.  Canvas, while it does perform fairly well when drawing lots of objects, is absolutely horrendous at drawing on a large space.  In Mockingbird, this meant that people with pages that were bigger than about 2000x2000 were suffering from choppiness and slowdowns, and as soon as I just removed drawing on a canvas element this big, things sped up tremendously.  This is a blog post that has some numbers and graphs.

For those that drink the Cappuccino, Cappuccino will call lockFocus on a view when the view needs display, even if the view has nothing in drawRect.  This means that if you have a giant CPView, even if you don't implement drawRect on it, a giant canvas element gets created in lockFocus.  It's not a huge performance hit, but getting rid of the lockFocus call did speed things up an a lower powered Macbook running Safari.  I made the following view subclass to do this:

Really simple - by default, a BigView will not draw anything.  But you can still set the isDrawable flag to make it call your drawRect method.  

EDIT: Just talked with the guys at 280 North and they explained to me that Cappuccino is, in fact, smart about not calling lockFocus unnecessarily.  The reason for the performance differences on the Macbook Pro vs the Macbook was that the code on the Macbook Pro did not have a drawRect method at all, whereas the code on the Macbook had an empty drawRect method on the view.  Cappuccino, apparently, figures out if your CPView subclass has an overridden drawRect method before calling lockFocus and all the other drawing code.  So, the default CPView already optimizes for this (just don't have an empty drawRect method in your CPView subclass).

Hope this helps anyone else running into performance problems with the Canvas tag.

Posted
 

Three things I did to tackle Mockingbird performance issues

I posted an update to Mockingbird earlier today that should make loading large projects much faster.  Most of what I did is probably fairly obvious, but I thought I should share what I did anyway for any Cappuccino newbies looking to help the performance of their app.

1. Use the Safari profiler

The first step to optimization is to always, ALWAYS profile your app.  The things slowing you down will often surprise you, and the guys at 280North have done an amazing job to make Safari's profile very usable with Cappuccino.  You can read more about what they've done here.  After using the profiler, I found a few easy ways to speed things up, but no matter what I did, some of the larger projects were still giving me slow script errors on Firefox.  It was at this point that I realized that if I could somehow break up the work that's being done up front when a new project is loaded, things would work out much better.  And then I remembered that I could...

2. Use Javascript's setTimeout method to break up a large chunk of work

The main issue with loading large projects up front is that each page was being recreated serially in one go.  Instead of doing this, I used setTimeout to load each page, and then give the browser a break between each page load.  My function was basically something like:

- (void)loadPageFunction { window.setTimeout(function() { [self loadPageFunction]; }, 0); }

This way, the browser doesn't think that loading all the pages is one giant chunk of work and, so, for large projects, unless the project has a really crazed page, the browser shouldn't complain.

But then, I noticed one more odd thing - whenever I loaded a project after already having a large project, things ran much more slowly.  After using the profiler to see what was going on, I noticed that my app was spending a lot of time cleaning up notifications from the old project.  After some investigation, I found out that the implementation for - [CPNotificationCenter removeObserver:] is way slower than explicitly removing your observer for each notification using - [CPNotificationtCenter removeObserver:name:object:], and I was using the former everywhere.  So, I decided to change these to...

3. Use - [CPNotificationtCenter removeObserver:name:object:] instead of - [CPNotificationCenter removeObserver:]

One of the problems with Javascript is its lack of weak references, which becomes an issue with singletons like CPNotificationCenter's defaultCenter.  I've tried fairly hard to make sure I always remove my observers wherever I set them up, but I'm pretty sure there are still leaks in there somewhere.  These leaks don't really affect performance until you use a - [CPNotificationCenter removeObserver:] call since this will end up iterating through all your notifications (so if you have many, that becomes a problem).  This is, however, a hack around a more fundamental problem - either we need to figure out a way to implement some kind of weak referencing system in Objective-J or, (the easier route), make it easier to not use the default notification center to handle notifications.  A lot of classes internal to the framework use the default notification center for messaging, but it would definitely be a lot easier for me to just trash the notification center whenever I open a new project (still not perfect, I know, but I think better than what currently happens).  

Posted
 

A small step towards better text support in Cappuccino

Though Cappuccino has treated me very well so far, text support in Cappuccino is still an area that needs much improvement.  Many newcomers still ask about multiline text support, and so far, the solution has been the minimal implementation of CPTextView located here.  I've been using this for a while, but recently ran into a few problems, and decided to make it just a little less hacked together =).

First, the code:  http://gist.github.com/239696

This code is a bit of an amalgam of code from CPTextField and the old CPTextView.  I've tried to make it a bit more cross-browser compatible and have added support for tabbing.  I've also changed the behavior to be based roughly on the behavior Ross Boucher implemented for CPTextField as described here.  I implement the same delegate methods, except that for CPTextView, the action fires on escape instead of return.  Note that this is not actually the behavior of CPTextView in Cocoa, so if you want to override this behavior, I've made sure to let CPTextView be easily overridable.  There are no file-scoped functions - you can just override the private methods _keyUpFunction, _keyDownFunction, _keyPressFunction and _blurFunction. 

There's still obviously a lot lacking in the code.  For one, I didn't really adhere strictly to the Cocoa API for CPTextView when writing this.  Additionally, autoscrolling doesn't work, entering text is fairly slow after scrollbars appear, and I would like to implement mouse tracking so that clicking on the CPTextView's scroll bar doesn't fire the blur method for the CPTextView.  But this is doing the job for now on Mockingbird.

Posted